msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: OperationalStaking may not possess enough CQT for the last withdrawal
Severity: medium

Description:
Both `_sharesToTokens` and `_tokensToShares` round down instead of rounding off against the user. This can result in users withdrawing few weis more than they should, which in turn would make the last CQT transfer from the contract revert due to insufficient balance.\\nWhen users `stake`, the shares they will receive is calculated via _tokensToShares:\\n```\\n    function _tokensToShares(\\n        uint128 amount,\\n        uint128 rate\\n    ) internal view returns (uint128) {\\n        return uint128((uint256(amount) * DIVIDER) / uint256(rate));\\n    }\\n```\\n\\nSo the rounding will be against the user, or zero if the user provided the right amount of CQT.\\nWhen users unstake, their shares are decreased by\\n```\\n    function _sharesToTokens(\\n        uint128 sharesN,\\n        uint128 rate\\n    ) internal view returns (uint128) {\\n        return uint128((uint256(sharesN) * uint256(rate)) / DIVIDER);\\n    }\\n```\\n\\nSo it is possible to `stake` and `unstake` such amounts, that would leave dust amount of shares on user's balance after their full withdrawal. However, dust amounts can not be withdrawn due to the check in _redeemRewards:\\n```\\n        require(\\n            effectiveAmount >= REWARD_REDEEM_THRESHOLD,\\n            "Requested amount must be higher than redeem threshold"\\n        );\\n```\\n\\nBut, if the user does not withdraw immediately, but instead does it after the multiplier is increased, the dust he received from rounding error becomes withdrawable, because his `totalUnlockedValue` becomes greater than `REWARD_REDEEM_THRESHOLD`.\\nSo the user will end up withdrawing more than their `initialStake + shareOfRewards`, which means, if the rounding after all other operations stays net-zero for the protocol, there won't be enough CQT for the last CQT withdrawal (be it `transferUnstakedOut`, `redeemRewards`, or redeemCommission).\\nFoundry PoC

Impact:
Victim's transactions will keep reverting unless they figure out that they need to decrease their withdrawal amount.

Recommended mitigation:
`_sharesToTokens` and `_tokensToShares`, instead of rounding down, should always round off against the user.
Vulnerability: Frontrunning validator freeze to withdraw tokens
Severity: medium

Description:
Covalent implements a freeze mechanism to disable malicious Validators, this allows the protocol to block all interactions with a validator when he behaves maliciously. Covalent also implements a timelock to ensure tokens are only withdraw after a certain amount of time. After the cooldown ends, tokens can always be withdrawn.\\nFollowing problem arise now: because the tokens can always be withdrawn, a malicious Validator can listen for a potential "freeze" transaction in the mempool, front run this transaction to unstake his tokens and withdraw them after the cooldown end.\\nAlmost every action on the Operational Staking contract checks if the validator is frozen or not:\\n```\\n require(!v.frozen, "Validator is frozen");\\n```\\n\\nThe methods transferUnstakedOut() and recoverUnstaking() are both not checking for this, making the unstake transaction front runnable. Here are the only checks of transferUnstakedOut():\\n```\\nrequire(validatorId < validatorsN, "Invalid validator");\\n        require(_validators[validatorId].unstakings[msg.sender].length > unstakingId, "Unstaking does not exist");\\n        Unstaking storage us = _validators[validatorId].unstakings[msg.sender][unstakingId];\\n        require(us.amount >= amount, "Unstaking has less tokens");\\n```\\n\\nThis makes following attack possible:\\nValidator cheats and gets rewarded fees.\\nProtocol notices the misbehavior and initiates a Freeze transaction\\nValidator sees the transaction and starts a unstake() transaction with higher gas.\\nValidator gets frozen, but the unstaking is already done\\nValidator waits for cooldown and withdraws tokens.\\nNow the validator has gained unfairly obtained tokens and withdrawn his stake.

Impact:
Malicious validators can front run freeze to withdraw tokens.

Recommended mitigation:
Implement a check if validator is frozen on `transferUnstakedOut()` and `recoverUnstaking()`, and revert transaction if true.\\nIf freezing all unstakings is undesirable (e.g. not freezing honest unstakes), the sponsor may consider storing the unstake timestamp as well:\\nStore the unstaking block number for each unstake.\\nFreeze the validator from a certain past block only, only unstakings that occur from that block onwards will get frozen.
Vulnerability: `validatorMaxStake` can be bypassed by using `setValidatorAddress()`
Severity: medium

Description:
`setValidatorAddress()` allows a validator to migrate to a new address of their choice. However, the current logic only stacks up the old address' stake to the new one, never checking `validatorMaxStake`.\\nThe current logic for `setValidatorAddress()` is as follow:\\n```\\nfunction setValidatorAddress(uint128 validatorId, address newAddress) external whenNotPaused {\\n    // // rest of code\\n    v.stakings[newAddress].shares += v.stakings[msg.sender].shares;\\n    v.stakings[newAddress].staked += v.stakings[msg.sender].staked;\\n    delete v.stakings[msg.sender];\\n    // // rest of code\\n}\\n```\\n\\nThe old address' stake is simply stacked on top of the new address' stake. There are no other checks for this amount, even though the new address may already have contained a stake.\\nThen the combined total of the two stakings may exceed `validatorMaxStake`. This accordingly allows the new (validator) staker's amount to bypass said threshold, breaking an important invariant of the protocol.\\nBob the validator has a self-stake equal to `validatorMaxStake`.\\nBob has another address, B2, with some stake delegated to Bob's validator.\\nBob migrates to B2.\\nBob's stake is stacked on top of B2. B2 becomes the new validator address, but their stake has exceeded `validatorMaxStake`.\\nB2 can then repeated this procedure to addresses B3, B4, ..., despite B2 already holding more than the max allowed amount.\\nBob now holds more stake than he should be able to, allowing him to earn an unfair amount of rewards compared to other validators.\\nWe also note that, even if the admin tries to freeze Bob, he can front-run the freeze with an unstake, since unstakes are not blocked from withdrawing (after cooldown ends).

Impact:
Breaking an important invariant of the protocol.\\nAllowing any validator to bypass the max stake amount. In turn allows them to earn an unfair amount of validator rewards in the process.\\nAllows a validator to unfairly increase their max delegator amount, as an effect of increasing `(validator stake) * maxCapMultiplier`.

Recommended mitigation:
Check that the new address's total stake does not exceed `validatorMaxStake` before proceeding with the migration.
Vulnerability: Nobody can cast for any proposal
Severity: medium

Description:
```\\nFile: bophades\\src\\external\\governance\\GovernorBravoDelegate.sol\\n    function castVoteInternal(\\n        address voter,\\n        uint256 proposalId,\\n        uint8 support\\n    ) internal returns (uint256) {\\n// rest of code// rest of code\\n        // Get the user's votes at the start of the proposal and at the time of voting. Take the minimum.\\n        uint256 originalVotes = gohm.getPriorVotes(voter, proposal.startBlock);\\n446:->       uint256 currentVotes = gohm.getPriorVotes(voter, block.number);\\n        uint256 votes = currentVotes > originalVotes ? originalVotes : currentVotes;\\n// rest of code// rest of code\\n    }\\n```\\n\\n```\\nfunction getPriorVotes(address account, uint256 blockNumber) external view returns (uint256) {\\n->      require(blockNumber < block.number, "gOHM::getPriorVotes: not yet determined");\\n// rest of code// rest of code\\n    }\\n```\\n\\nTherefore, L446 will always revert. Voting will not be possible.\\nCopy the coded POC below to one project from Foundry and run `forge test -vvv` to prove this issue.

Impact:
Nobody can cast for any proposal. Not being able to vote means the entire governance contract will be useless. Core functionality is broken.

Recommended mitigation:
```\\nFile: bophades\\src\\external\\governance\\GovernorBravoDelegate.sol\\n        uint256 originalVotes = gohm.getPriorVotes(voter, proposal.startBlock);\\n446:-        uint256 currentVotes = gohm.getPriorVotes(voter, block.number);\\n446:+        uint256 currentVotes = gohm.getPriorVotes(voter, block.number - 1);\\n        uint256 votes = currentVotes > originalVotes ? originalVotes : currentVotes;\\n```\\n\\n
Vulnerability: User can get free entries if the price of any whitelisted ERC20 token is greater than the round's `valuePerEntry`
Severity: high

Description:
Lack of explicit separation between ERC20 and ERC721 deposits allows users to gain free entries for any round given there exists a whitelisted ERC20 token with price greater than the round's `valuePerEntry`.\\n```\\n                if (isCurrencyAllowed[tokenAddress] != 1) {\\n                    revert InvalidCollection();\\n                }\\n```\\n\\n```\\n                if (singleDeposit.tokenType == YoloV2__TokenType.ERC721) {\\n                    if (price == 0) {\\n                        price = _getReservoirPrice(singleDeposit);\\n                        prices[tokenAddress][roundId] = price;\\n                    }\\n```\\n\\n```\\n                    uint256 entriesCount = price / round.valuePerEntry;\\n                    if (entriesCount == 0) {\\n                        revert InvalidValue();\\n                    }\\n```\\n\\n```\\n            } else if (tokenType == TokenType.ERC721) {\\n                for (uint256 j; j < itemIdsLengthForSingleCollection; ) {\\n                    // rest of code\\n                    _executeERC721TransferFrom(items[i].tokenAddress, from, to, itemIds[j]);\\n```\\n\\n```\\n    function _executeERC721TransferFrom(address collection, address from, address to, uint256 tokenId) internal {\\n        // rest of code\\n        (bool status, ) = collection.call(abi.encodeCall(IERC721.transferFrom, (from, to, tokenId)));\\n        // rest of code\\n    }\\n```\\n\\nThe function signature of `transferFrom` for ERC721 and ERC20 is identical, so this will call `transferFrom` on the ERC20 contract with `amount = 0` (since 'token ids' specified in `singleDeposit.tokenIdsOrAmounts` are all 0). Consequently, the user pays nothing and the transaction executes successfully (as long as the ERC20 token does not revert on zero transfers).\\nPaste the test below into `Yolo.deposit.t.sol` with `forge-std/console.sol` imported. It demonstrates a user making 3 free deposits (in the same transaction) using the MKR token (ie. with zero MKR balance). The token used can be substituted with any token with price > `valuePerEntry = 0.01 ETH` (which is non-rebasing/non-taxable and has sufficient liquidity in their /ETH Uniswap v3 pool as specified in the README).\\n

Impact:
Users can get an arbitrary number of entries into rounds for free (which should generally allow them to significantly increase their chances of winning). In the case the winner is a free depositor, they will end up with the same profit as if they participated normally since they have to pay the fee over the total value of the deposits (which includes the price of their free deposits). If the winner is an honest depositor, they still have to pay the full fee including the free entries, but they are unable to claim the value for the free entries (since the `tokenId` (or amount) is zero). They earn less profit than if everyone had participated honestly.

Recommended mitigation:
Whitelist tokens using both the token address and the token type (ERC20/ERC721).
Vulnerability: Low precision is used when checking spot price deviation
Severity: medium

Description:
Low precision is used when checking spot price deviation, which might lead to potential manipulation or create the potential for an MEV opportunity due to valuation discrepancy.\\nAssume the following:\\nThe max deviation is set to 1%\\n`nTokenOracleValue` is 1,000,000,000\\n`nTokenSpotValue` is 980,000,001\\n```\\nFile: Constants.sol\\n    // Basis for percentages\\n    int256 internal constant PERCENTAGE_DECIMALS = 100;\\n```\\n\\n```\\nFile: nTokenCalculations.sol\\n            int256 maxValueDeviationPercent = int256(\\n                uint256(uint8(nToken.parameters[Constants.MAX_MINT_DEVIATION_LIMIT]))\\n            );\\n            // Check deviation limit here\\n            int256 deviationInPercentage = nTokenOracleValue.sub(nTokenSpotValue).abs()\\n                .mul(Constants.PERCENTAGE_DECIMALS).div(nTokenOracleValue);\\n            require(deviationInPercentage <= maxValueDeviationPercent, "Over Deviation Limit");\\n```\\n\\nBased on the above formula:\\n```\\nnTokenOracleValue.sub(nTokenSpotValue).abs().mul(Constants.PERCENTAGE_DECIMALS).div(nTokenOracleValue);\\n((nTokenOracleValue - nTokenSpotValue) * Constants.PERCENTAGE_DECIMALS) / nTokenOracleValue\\n((1,000,000,000 - 980,000,001) * 100) / 1,000,000,000\\n(19,999,999 * 100) / 1,000,000,000\\n1,999,999,900 / 1,000,000,000 = 1.9999999 = 1\\n```\\n\\nThe above shows that the oracle and spot values have deviated by 1.99999%, which is close to 2%. However, due to a rounding error, it is rounded down to 1%, and the TX will not revert.

Impact:
The purpose of the deviation check is to ensure that the spot market value is not manipulated. If the deviation check is not accurate, it might lead to potential manipulation or create the potential for an MEV opportunity due to valuation discrepancy.

Recommended mitigation:
Consider increasing the precision.\\nFor instance, increasing the precision from `Constants.PERCENTAGE_DECIMALS` (100) to 1e8 would have caught the issue mentioned earlier in the report even after the rounding down.\\n```\\nnTokenOracleValue.sub(nTokenSpotValue).abs().mul(1e8).div(nTokenOracleValue);\\n((nTokenOracleValue - nTokenSpotValue) * 1e8) / nTokenOracleValue\\n((1,000,000,000 - 980,000,001) * 1e8) / 1,000,000,000\\n(19,999,999 * 1e8) / 1,000,000,000 = 1999999.9 = 1999999\\n```\\n\\n1% of 1e8 = 1000000\\n```\\nrequire(deviationInPercentage <= maxValueDeviationPercent, "Over Deviation Limit")\\nrequire(1999999 <= 1000000, "Over Deviation Limit") => Revert\\n```\\n
Vulnerability: All funds can be stolen from JOJODealer
Severity: high

Description:
`Funding._withdraw()` makes arbitrary call with user specified params. User can for example make ERC20 to himself and steal funds.\\nUser can specify parameters `param` and `to` when withdraws:\\n```\\n    function executeWithdraw(address from, address to, bool isInternal, bytes memory param) external nonReentrant {\\n        Funding.executeWithdraw(state, from, to, isInternal, param);\\n    }\\n```\\n\\nIn the end of `_withdraw()` function address `to` is called with that bytes param:\\n```\\n    function _withdraw(\\n        Types.State storage state,\\n        address spender,\\n        address from,\\n        address to,\\n        uint256 primaryAmount,\\n        uint256 secondaryAmount,\\n        bool isInternal,\\n        bytes memory param\\n    )\\n        private\\n    {\\n        // rest of code\\n\\n        if (param.length != 0) {\\n          require(Address.isContract(to), "target is not a contract");\\n            (bool success,) = to.call(param);\\n            if (success == false) {\\n                assembly {\\n                    let ptr := mload(0x40)\\n                    let size := returndatasize()\\n                    returndatacopy(ptr, 0, size)\\n                    revert(ptr, size)\\n                }\\n            }\\n        }\\n    }\\n```\\n\\nAs an attack vector attacker can execute withdrawal of 1 wei to USDC contract and pass calldata to transfer arbitrary USDC amount to himself via USDC contract.

Impact:
All funds can be stolen from JOJODealer

Recommended mitigation:
Don't make arbitrary call with user specified params
Vulnerability: FundingRateArbitrage contract can be drained due to rounding error
Severity: high

Description:
In the `requestWithdraw`, rounding in the wrong direction is done which can lead to contract being drained.\\nIn the `requestWithdraw` function in `FundingRateArbitrage`, we find the following lines of code:\\n```\\njusdOutside[msg.sender] -= repayJUSDAmount;\\nuint256 index = getIndex();\\nuint256 lockedEarnUSDCAmount = jusdOutside[msg.sender].decimalDiv(index);\\nrequire(\\n     earnUSDCBalance[msg.sender] >= lockedEarnUSDCAmount, "lockedEarnUSDCAmount is bigger than earnUSDCBalance"\\n);\\nwithdrawEarnUSDCAmount = earnUSDCBalance[msg.sender] - lockedEarnUSDCAmount;\\n```\\n\\nBecause we round down when calculating `lockedEarnUSDCAmount`, `withdrawEarnUSDCAmount` is higher than it should be, which leads to us allowing the user to withdraw more than we should allow them to given the amount of JUSD they repaid.\\nThe execution of this is a bit more complicated, let's go through an example. We will assume there's a bunch of JUSD existing in the contract and the attacker is the first to deposit.\\nSteps:\\nThe attacker deposits `1` unit of USDC and then manually sends in another 100 * 10^6 - `1` (not through deposit, just a transfer). The share price / price per earnUSDC will now be $100. Exactly one earnUSDC is in existence at the moment.\\nNext the attacker creates a new EOA and deposits a little over $101 worth of USDC (so that after fees we can get to the $100), giving one earnUSDC to the EOA. The attacker will receive around $100 worth of `JUSD` from doing this.\\nAttacker calls `requestWithdraw` with `repayJUSDAmount` = `1` with the second newly created EOA\\n`lockedEarnUSDCAmount` is rounded down to 0 (since `repayJUSDAmount` is subtracted from jusdOutside[msg.sender]\\n`withdrawEarnUSDCAmount` will be `1`\\nAfter `permitWithdrawRequests` is called, attacker will be able to withdraw the $100 they deposited through the second EOA (granted, they lost the deposit and withdrawal fees) while only having sent `1` unit of `JUSD` back. This leads to massive profit for the attacker.\\nAttacker can repeat steps 2-6 constantly until the contract is drained of JUSD.

Impact:
All JUSD in the contract can be drained

Recommended mitigation:
Round up instead of down
Vulnerability: `JUSDBankStorage::getTRate()`,`JUSDBankStorage::accrueRate()` are calculated differently, and the data calculation is biased, Causes the `JUSDBank` contract funciton result to be incorrect
Severity: medium

Description:
```\\n    function accrueRate() public {\\n        uint256 currentTimestamp = block.timestamp;\\n        if (currentTimestamp == lastUpdateTimestamp) {\\n            return;\\n        }\\n        uint256 timeDifference = block.timestamp - uint256(lastUpdateTimestamp);\\n         tRate = tRate.decimalMul((timeDifference * borrowFeeRate) / Types.SECONDS_PER_YEAR + 1e18);\\n        lastUpdateTimestamp = currentTimestamp;\\n    }\\n\\n    function getTRate() public view returns (uint256) {\\n        uint256 timeDifference = block.timestamp - uint256(lastUpdateTimestamp);\\n        return tRate + (borrowFeeRate * timeDifference) / Types.SECONDS_PER_YEAR;\\n    }\\n```\\n\\nJUSDBankStorage::getTRate(),JUSDBankStorage::accrueRate() are calculated differently, and the data calculation is biased, resulting in the JUSDBank contract not being executed correctly\\nThe wrong result causes the funciton calculation results of `JUSDBank::_isAccountSafe()`, `JUSDBank::flashLoan()`, `JUSDBank::_handleBadDebt`, etc. to be biased,and all functions that call the relevant function will be biased

Impact:
Causes the `JUSDBank` contract funciton result to be incorrect

Recommended mitigation:
Use the same calculation formula:\\n```\\n    function accrueRate() public {\\n        uint256 currentTimestamp = block.timestamp;\\n        if (currentTimestamp == lastUpdateTimestamp) {\\n            return;\\n        }\\n        uint256 timeDifference = block.timestamp // Remove the line below\\n uint256(lastUpdateTimestamp);\\n         tRate = tRate.decimalMul((timeDifference * borrowFeeRate) / Types.SECONDS_PER_YEAR // Add the line below\\n 1e18);\\n        lastUpdateTimestamp = currentTimestamp;\\n    }\\n\\n    function getTRate() public view returns (uint256) {\\n        uint256 timeDifference = block.timestamp // Remove the line below\\n uint256(lastUpdateTimestamp);\\n// Remove the line below\\n       return tRate // Add the line below\\n (borrowFeeRate * timeDifference) / Types.SECONDS_PER_YEAR;\\n// Add the line below\\n       return  tRate.decimalMul((timeDifference * borrowFeeRate) / Types.SECONDS_PER_YEAR // Add the line below\\n 1e18);\\n    }\\n```\\n
Vulnerability: Funding#requestWithdraw uses incorrect withdraw address
Severity: medium

Description:
When requesting a withdraw, `msg.sender` is used in place of the `from` address. This means that withdraws cannot be initiated on behalf of other users. This will break integrations that depend on this functionality leading to irretrievable funds.\\nFunding.sol#L69-L82\\n```\\nfunction requestWithdraw(\\n    Types.State storage state,\\n    address from,\\n    uint256 primaryAmount,\\n    uint256 secondaryAmount\\n)\\n    external\\n{\\n    require(isWithdrawValid(state, msg.sender, from, primaryAmount, secondaryAmount), Errors.WITHDRAW_INVALID);\\n    state.pendingPrimaryWithdraw[msg.sender] = primaryAmount;\\n    state.pendingSecondaryWithdraw[msg.sender] = secondaryAmount;\\n    state.withdrawExecutionTimestamp[msg.sender] = block.timestamp + state.withdrawTimeLock;\\n    emit RequestWithdraw(msg.sender, primaryAmount, secondaryAmount, state.withdrawExecutionTimestamp[msg.sender]);\\n}\\n```\\n\\nAs shown above the withdraw is accidentally queue to `msg.sender` NOT the `from` address. This means that all withdraws started on behalf of another user will actually trigger a withdraw `from` the `operator`. The result is that withdraw cannot be initiated on behalf of other users, even if the allowance is set properly, leading to irretrievable funds

Impact:
Requesting withdraws for other users is broken and strands funds

Recommended mitigation:
Change all occurrences of `msg.sender` in stage changes to `from` instead.