msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: IdleCDO.harvest() allows price manipulation in certain circumstances
Severity: medium

Description:
The function `IdleCDO.harvest()` uses Uniswap to liquidate rewards earned by the contract's strategy, then updates the relevant positions and internal accounting. This function can only be called by the contract `owner` or the designated `rebalancer` address, and it accepts an array which indicates the minimum buy amounts for the liquidation of each reward token.\\nThe purpose of permissioning this method and specifying minimum buy amounts is to prevent a sandwiching attack from manipulating the reserves of the Uniswap pools and forcing the `IdleCDO` contract to incur loss due to price slippage.\\nHowever, this does not effectively prevent price manipulation in all cases. Because the contract sells it's entire balance of redeemed rewards for the specified minimum buy amount, this approach does not enforce a minimum price for the executed trades. If the balance of `IdleCDO` or the amount of claimable rewards increases between the submission of the `harvest()` transaction and its execution, it may be possible to perform a profitable sandwiching attack while still satisfying the required minimum buy amounts.\\nThe viability of this exploit depends on how effectively an attacker can increase the amount of rewards tokens to be sold without incurring an offsetting loss. The strategy contracts used by `IdleCDO` are expected to vary widely in their implementations, and this manipulation could potentially be done either through direct interaction with the protocol or as part of a flashbots bundle containing a large position adjustment from an honest user.\\n```\\nfunction harvest(bool \\_skipRedeem, bool \\_skipIncentivesUpdate, bool[] calldata \\_skipReward, uint256[] calldata \\_minAmount) external {\\n  require(msg.sender == rebalancer || msg.sender == owner(), "IDLE:!AUTH");\\n```\\n\\n```\\n// approve the uniswap router to spend our reward\\nIERC20Detailed(rewardToken).safeIncreaseAllowance(address(\\_uniRouter), \\_currentBalance);\\n// do the uniswap trade\\n\\_uniRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(\\n  \\_currentBalance,\\n  \\_minAmount[i],\\n  \\_path,\\n  address(this),\\n  block.timestamp + 1\\n);\\n```\\n

Impact:

Recommended mitigation:
Update `IdleCDO.harvest()` to enforce a minimum price rather than a minimum buy amount. One method of doing so would be taking an additional array parameter indicating the amount of each token to sell in exchange for the respective buy amount.
Vulnerability: Missing Sanity checks
Severity: low

Description:
The implementation of `initialize()` functions are missing some sanity checks. The proper checks are implemented in some of the setter functions but missing in some others.\\nMissing sanity check for `!= address(0)`\\n```\\ntoken = \\_guardedToken;\\nstrategy = \\_strategy;\\nstrategyToken = IIdleCDOStrategy(\\_strategy).strategyToken();\\nrebalancer = \\_rebalancer;\\n```\\n\\n```\\nguardian = \\_owner;\\n```\\n\\n```\\naddress \\_currAAStaking = AAStaking;\\naddress \\_currBBStaking = BBStaking;\\n```\\n\\n```\\nidleCDO = \\_idleCDO;\\ntranche = \\_trancheToken;\\nrewards = \\_rewards;\\ngovernanceRecoveryFund = \\_governanceRecoveryFund;\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nThe development team has addressed this concern in commit `a1d5dac0ad5f562d4c75bff99e770d92bcc2a72f`. This change has not been reviewed by the audit team.\\nAdd sanity checks before assigning system variables.
Vulnerability: Frontrunning attacks by the owner
Severity: high

Description:
There are few possible attack vectors by the owner:\\nAll strategies have fees from rewards. In addition to that, the PancakeSwap strategy has deposit fees. The default deposit fees equal zero; the maximum is limited to 5%:\\n```\\nuint256 constant MAXIMUM\\_DEPOSIT\\_FEE = 5e16; // 5%\\nuint256 constant DEFAULT\\_DEPOSIT\\_FEE = 0e16; // 0%\\n  \\nuint256 constant MAXIMUM\\_PERFORMANCE\\_FEE = 50e16; // 50%\\nuint256 constant DEFAULT\\_PERFORMANCE\\_FEE = 10e16; // 10%\\n```\\n\\nWhen a user deposits tokens, expecting to have zero deposit fees, the `owner` can frontrun the deposit and increase fees to 5%. If the deposit size is big enough, that may be a significant amount of money. 2. In the `gulp` function, the reward tokens are exchanged for the reserve tokens on the exchange:\\n```\\nfunction gulp(uint256 \\_minRewardAmount) external onlyEOAorWhitelist nonReentrant\\n{\\n  uint256 \\_pendingReward = \\_getPendingReward();\\n  if (\\_pendingReward > 0) {\\n      \\_withdraw(0);\\n  }\\n  {\\n      uint256 \\_totalReward = Transfers.\\_getBalance(rewardToken);\\n      uint256 \\_feeReward = \\_totalReward.mul(performanceFee) / 1e18;\\n      Transfers.\\_pushFunds(rewardToken, collector, \\_feeReward);\\n  }\\n  if (rewardToken != routingToken) {\\n      require(exchange != address(0), "exchange not set");\\n      uint256 \\_totalReward = Transfers.\\_getBalance(rewardToken);\\n      Transfers.\\_approveFunds(rewardToken, exchange, \\_totalReward);\\n      IExchange(exchange).convertFundsFromInput(rewardToken, routingToken, \\_totalReward, 1);\\n  }\\n  if (routingToken != reserveToken) {\\n      require(exchange != address(0), "exchange not set");\\n      uint256 \\_totalRouting = Transfers.\\_getBalance(routingToken);\\n      Transfers.\\_approveFunds(routingToken, exchange, \\_totalRouting);\\n      IExchange(exchange).joinPoolFromInput(reserveToken, routingToken, \\_totalRouting, 1);\\n  }\\n  uint256 \\_totalBalance = Transfers.\\_getBalance(reserveToken);\\n  require(\\_totalBalance >= \\_minRewardAmount, "high slippage");\\n  \\_deposit(\\_totalBalance);\\n}\\n```\\n\\nThe `owner` can change the `exchange` parameter to the malicious address that steals tokens. The `owner` then calls `gulp` with `_minRewardAmount==0`, and all the rewards will be stolen. The same attack can be implemented in fee collectors and the buyback contract.

Impact:

Recommended mitigation:
Resolution\\nThe client communicated this issue was addressed in commit 34c6b355795027d27ae6add7360e61eb6b01b91b.\\nUse a timelock to avoid instant changes of the parameters.
Vulnerability: Expected amounts of tokens in the withdraw function
Severity: medium

Description:
Every `withdraw` function in the strategy contracts is calculating the expected amount of the returned tokens before withdrawing them:\\n```\\nfunction withdraw(uint256 \\_shares, uint256 \\_minAmount) external onlyEOAorWhitelist nonReentrant\\n{\\n address \\_from = msg.sender;\\n (uint256 \\_amount, uint256 \\_withdrawalAmount, uint256 \\_netAmount) = \\_calcAmountFromShares(\\_shares);\\n require(\\_netAmount >= \\_minAmount, "high slippage");\\n \\_burn(\\_from, \\_shares);\\n \\_withdraw(\\_amount);\\n Transfers.\\_pushFunds(reserveToken, \\_from, \\_withdrawalAmount);\\n}\\n```\\n\\nAfter that, the contract is trying to transfer this pre-calculated amount to the `msg.sender`. It is never checked whether the intended amount was actually transferred to the strategy contract. If the amount is lower, that may result in reverting the `withdraw` function all the time and locking up tokens.\\nEven though we did not find any specific case of returning a different amount of tokens, it is still a good idea to handle this situation to minimize relying on the security of the external contracts.

Impact:

Recommended mitigation:
Resolution\\nClient's statement : “This issue did not really need fixing. The mitigation was already in place by depositing a tiny amount of the reserve into the contract, if necessary”\\nThere are a few options how to mitigate the issue:\\nDouble-check the balance difference before and after the MasterChef's `withdraw` function is called.\\nHandle this situation in the emergency mode (https://github.com/ConsenSys/growthdefi-audit-2021-06/issues/11).
Vulnerability: The capping mechanism for Panther token leads to increased fees
Severity: medium

Description:
Panther token has a cap in transfer sizes, so any transfer in the contract is limited beforehand:\\n```\\nfunction gulp(uint256 \\_minRewardAmount) external onlyEOAorWhitelist nonReentrant\\n{\\n uint256 \\_pendingReward = \\_getPendingReward();\\n if (\\_pendingReward > 0) {\\n  \\_withdraw(0);\\n }\\n uint256 \\_\\_totalReward = Transfers.\\_getBalance(rewardToken);\\n (uint256 \\_feeReward, uint256 \\_retainedReward) = \\_capFeeAmount(\\_\\_totalReward.mul(performanceFee) / 1e18);\\n Transfers.\\_pushFunds(rewardToken, buyback, \\_feeReward);\\n if (rewardToken != routingToken) {\\n  require(exchange != address(0), "exchange not set");\\n  uint256 \\_totalReward = Transfers.\\_getBalance(rewardToken);\\n  \\_totalReward = \\_capTransferAmount(rewardToken, \\_totalReward, \\_retainedReward);\\n  Transfers.\\_approveFunds(rewardToken, exchange, \\_totalReward);\\n  IExchange(exchange).convertFundsFromInput(rewardToken, routingToken, \\_totalReward, 1);\\n }\\n if (routingToken != reserveToken) {\\n  require(exchange != address(0), "exchange not set");\\n  uint256 \\_totalRouting = Transfers.\\_getBalance(routingToken);\\n  \\_totalRouting = \\_capTransferAmount(routingToken, \\_totalRouting, \\_retainedReward);\\n  Transfers.\\_approveFunds(routingToken, exchange, \\_totalRouting);\\n  IExchange(exchange).joinPoolFromInput(reserveToken, routingToken, \\_totalRouting, 1);\\n }\\n uint256 \\_totalBalance = Transfers.\\_getBalance(reserveToken);\\n \\_totalBalance = \\_capTransferAmount(reserveToken, \\_totalBalance, \\_retainedReward);\\n require(\\_totalBalance >= \\_minRewardAmount, "high slippage");\\n \\_deposit(\\_totalBalance);\\n}\\n```\\n\\nFees here are calculated from the full amount of rewards (__totalReward ):\\n```\\n(uint256 \\_feeReward, uint256 \\_retainedReward) = \\_capFeeAmount(\\_\\_totalReward.mul(performanceFee) / 1e18);\\n```\\n\\nBut in fact, if the amount of the rewards is too big, it will be capped, and the residuals will be “taxed” again during the next call of the `gulp` function. That behavior leads to multiple taxations of the same tokens, which means increased fees.

Impact:

Recommended mitigation:
Resolution\\nThe client communicated this issue was addressed in commit 34c6b355795027d27ae6add7360e61eb6b01b91b.\\nThe best solution would be to cap `__totalReward` first and then calculate fees from the capped value.
Vulnerability: The _capFeeAmount function is not working as intended
Severity: medium

Description:
Panther token has a limit on the transfer size. Because of that, all the Panther transfer values in the `PantherSwapCompoundingStrategyToken` are also capped beforehand. The following function is called to cap the size of fees:\\n```\\nfunction \\_capFeeAmount(uint256 \\_amount) internal view returns (uint256 \\_capped, uint256 \\_retained)\\n{\\n \\_retained = 0;\\n uint256 \\_limit = \\_calcMaxRewardTransferAmount();\\n if (\\_amount > \\_limit) {\\n  \\_amount = \\_limit;\\n  \\_retained = \\_amount.sub(\\_limit);\\n }\\n return (\\_amount, \\_retained);\\n}\\n```\\n\\nThis function should return the capped amount and the amount of retained tokens. But because the `_amount` is changed before calculating the `_retained`, the retained amount will always be 0.

Impact:

Recommended mitigation:
Calculate the `retained` value before changing the `amount`.
Vulnerability: Stale split ratios in UniversalBuyback
Severity: medium

Description:
The `gulp` and `pendingBurning` functions of the `UniversalBuyback` contract use the hardcoded, constant values of `DEFAULT_REWARD_BUYBACK1_SHARE` and `DEFAULT_REWARD_BUYBACK2_SHARE` to determine the ratio the trade value is split with.\\nConsequently, any call to `setRewardSplit` to set a new ratio will be ineffective but still result in a `ChangeRewardSplit` event being emitted. This event can deceive system operators and users as it does not reflect the correct values of the contract.\\n```\\nuint256 \\_amount1 = \\_balance.mul(DEFAULT\\_REWARD\\_BUYBACK1\\_SHARE) / 1e18;\\nuint256 \\_amount2 = \\_balance.mul(DEFAULT\\_REWARD\\_BUYBACK2\\_SHARE) / 1e18;\\n```\\n\\n```\\nuint256 \\_amount1 = \\_balance.mul(DEFAULT\\_REWARD\\_BUYBACK1\\_SHARE) / 1e18;\\nuint256 \\_amount2 = \\_balance.mul(DEFAULT\\_REWARD\\_BUYBACK2\\_SHARE) / 1e18;\\n```\\n

Impact:

Recommended mitigation:
Instead of the default values, `rewardBuyback1Share` and `rewardBuyback2Share` should be used.
Vulnerability: Yearn: Re-entrancy attack during deposit
Severity: high

Description:
During the deposit in the `supplyTokenTo` function, the token transfer is happening after the shares are minted and before tokens are deposited to the yearn vault:\\n```\\nfunction supplyTokenTo(uint256 \\_amount, address to) override external {\\n    uint256 shares = \\_tokenToShares(\\_amount);\\n\\n    \\_mint(to, shares);\\n\\n    // NOTE: we have to deposit after calculating shares to mint\\n    token.safeTransferFrom(msg.sender, address(this), \\_amount);\\n\\n    \\_depositInVault();\\n\\n    emit SuppliedTokenTo(msg.sender, shares, \\_amount, to);\\n}\\n```\\n\\nIf the token allows the re-entrancy (e.g., ERC-777), the attacker can do one more transaction during the token transfer and call the `supplyTokenTo` function again. This second call will be done with already modified shares from the first deposit but non-modified token balances. That will lead to an increased amount of shares minted during the `supplyTokenTo`. By using that technique, it's possible to steal funds from other users of the contract.

Impact:

Recommended mitigation:
Have the re-entrancy guard on all the external functions.
Vulnerability: Yearn: Partial deposits are not processed properly
Severity: high

Description:
The deposit is usually made with all the token balance of the contract:\\n```\\n// this will deposit full balance (for cases like not enough room in Vault)\\nreturn v.deposit();\\n```\\n\\nThe Yearn vault contract has a limit of how many tokens can be deposited there. If the deposit hits the limit, only part of the tokens is deposited (not to exceed the limit). That case is not handled properly, the shares are minted as if all the tokens are accepted, and the “change” is not transferred back to the caller:\\n```\\nfunction supplyTokenTo(uint256 \\_amount, address to) override external {\\n    uint256 shares = \\_tokenToShares(\\_amount);\\n\\n    \\_mint(to, shares);\\n\\n    // NOTE: we have to deposit after calculating shares to mint\\n    token.safeTransferFrom(msg.sender, address(this), \\_amount);\\n\\n    \\_depositInVault();\\n\\n    emit SuppliedTokenTo(msg.sender, shares, \\_amount, to);\\n}\\n```\\n

Impact:

Recommended mitigation:
Handle the edge cases properly.
Vulnerability: Sushi: balanceOfToken underestimates balance
Severity: medium

Description:
The `balanceOfToken` computation is too pessimistic, i.e., it can underestimate the current balance slightly.\\n```\\n/// @notice Returns the total balance (in asset tokens). This includes the deposits and interest.\\n/// @return The underlying balance of asset tokens\\nfunction balanceOfToken(address addr) public override returns (uint256) {\\n    if (balances[addr] == 0) return 0;\\n    ISushiBar bar = ISushiBar(sushiBar);\\n\\n    uint256 shares = bar.balanceOf(address(this));\\n    uint256 totalShares = bar.totalSupply();\\n\\n    uint256 sushiBalance =\\n        shares.mul(ISushi(sushiAddr).balanceOf(address(sushiBar))).div(\\n            totalShares\\n        );\\n    uint256 sourceShares = bar.balanceOf(address(this));\\n\\n    return (balances[addr].mul(sushiBalance).div(sourceShares));\\n}\\n```\\n\\nFirst, it calculates the amount of SUSHI that “belongs to” the yield source contract (sushiBalance), and then it determines the fraction of that amount that would be owed to the address in question. However, the “belongs to” above is a purely theoretical concept; it never happens that the yield source contract as a whole redeems and then distributes that amount among its shareholders; instead, if a shareholder redeems tokens, their request is passed through to the `SushiBar`. So in reality, there's no reason for this two-step process, and the holder's balance of SUSHI is more accurately computed as `balances[addr].mul(ISushi(sushiAddr).balanceOf(address(sushiBar))).div(totalShares)`, which can be greater than what `balanceOfToken` currently returns. Note that this is the amount of SUSHI that `addr` could withdraw directly from the `SushiBar`, based on their amount of shares. Observe also that if we sum these numbers up over all holders in the yield source contract, the result is smaller than or equal to `sushiBalance`. So the sum still doesn't exceed what “belongs to” the yield source contract.

Impact:

Recommended mitigation:
The `balanceOfToken` function should use the formula above.
Vulnerability: Yearn: Redundant approve call
Severity: low

Description:
The approval for token transfer is done in the following way:\\n```\\nif(token.allowance(address(this), address(v)) < token.balanceOf(address(this))) {\\n    token.safeApprove(address(v), 0);\\n    token.safeApprove(address(v), type(uint256).max);\\n}\\n```\\n\\nSince the approval will be equal to the maximum value, there's no need to make zero-value approval first.

Impact:

Recommended mitigation:
Change two `safeApprove` to one regular `approve` with the maximum value.
Vulnerability: Sushi: Some state variables should be immutable and have more specific types
Severity: low

Description:
The state variables `sushiBar` and `sushiAddr` are initialized in the contract's constructor and never changed afterward.\\n```\\ncontract SushiYieldSource is IYieldSource {\\n    using SafeMath for uint256;\\n    address public sushiBar;\\n    address public sushiAddr;\\n    mapping(address => uint256) public balances;\\n\\n    constructor(address \\_sushiBar, address \\_sushiAddr) public {\\n        sushiBar = \\_sushiBar;\\n        sushiAddr = \\_sushiAddr;\\n    }\\n```\\n\\nThey should be immutable; that would save some gas and make it clear that they won't (and can't) be changed once the contract has been deployed.\\nMoreover, they would better have more specific interface types than `address`, i.e., `ISushiBar` for `sushiBar` and `ISushi` for `sushiAddr`. That would be safer and make the code more readable.

Impact:

Recommended mitigation:
Make these two state variables `immutable` and change their types as indicated above. Remove the corresponding explicit type conversions in the rest of the contract, and add explicit conversions to type `address` where necessary.
Vulnerability: Sushi: Unnecessary balance queries
Severity: low

Description:
In function `redeemToken`, `barBalanceDiff` is always the same as `requiredShares` because the SushiBar's `leave` function burns exactly `requiredShares` xSUSHI.\\n```\\nuint256 barBeforeBalance = bar.balanceOf(address(this));\\nuint256 sushiBeforeBalance = sushi.balanceOf(address(this));\\n\\nbar.leave(requiredShares);\\n\\nuint256 barAfterBalance = bar.balanceOf(address(this));\\nuint256 sushiAfterBalance = sushi.balanceOf(address(this));\\n\\nuint256 barBalanceDiff = barBeforeBalance.sub(barAfterBalance);\\nuint256 sushiBalanceDiff = sushiAfterBalance.sub(sushiBeforeBalance);\\n\\nbalances[msg.sender] = balances[msg.sender].sub(barBalanceDiff);\\n```\\n

Impact:

Recommended mitigation:
Use `requiredShares` instead of `barBalanceDiff`, and remove the unnecessary queries and variables.
Vulnerability: Sushi: Unnecessary function declaration in interface
Severity: low

Description:
The `ISushiBar` interface declares a `transfer` function.\\n```\\ninterface ISushiBar {\\n    function enter(uint256 \\_amount) external;\\n\\n    function leave(uint256 \\_share) external;\\n\\n    function totalSupply() external view returns (uint256);\\n\\n    function balanceOf(address account) external view returns (uint256);\\n\\n    function transfer(address recipient, uint256 amount)\\n        external\\n        returns (bool);\\n}\\n```\\n\\nHowever, this function is never used, so it could be removed from the interface. Other functions that the `SushiBar` provides but are not used (approve, for example) aren't part of the interface either.

Impact:

Recommended mitigation:
Remove the `transfer` declaration from the `ISushiBar` interface.
Vulnerability: Simplify the harvest method in each SinglePlus
Severity: low

Description:
The `BadgerSBTCCrvPlus` single plus contract implements a custom `harvest` method.\\n```\\n/\\*\\*\\n \\* @dev Harvest additional yield from the investment.\\n \\* Only governance or strategist can call this function.\\n \\*/\\nfunction harvest(address[] calldata \\_tokens, uint256[] calldata \\_cumulativeAmounts, uint256 \\_index, uint256 \\_cycle,\\n```\\n\\nThis method can only be called by the strategist because of the `onlyStrategist` modifier.\\nThis method has a few steps which take one asset and transform it into another asset a few times.\\nIt first claims the Badger tokens:\\n```\\n// 1. Harvest from Badger Tree\\nIBadgerTree(BADGER\\_TREE).claim(\\_tokens, \\_cumulativeAmounts, \\_index, \\_cycle, \\_merkleProof, \\_amountsToClaim);\\n```\\n\\nThen it transforms the Badger tokens into WBTC using Uniswap.\\n```\\n// 2. Sushi: Badger --> WBTC\\nuint256 \\_badger = IERC20Upgradeable(BADGER).balanceOf(address(this));\\nif (\\_badger > 0) {\\n    IERC20Upgradeable(BADGER).safeApprove(SUSHISWAP, 0);\\n    IERC20Upgradeable(BADGER).safeApprove(SUSHISWAP, \\_badger);\\n\\n    address[] memory \\_path = new address[](2);\\n    \\_path[0] = BADGER;\\n    \\_path[1] = WBTC;\\n\\n    IUniswapRouter(SUSHISWAP).swapExactTokensForTokens(\\_badger, uint256(0), \\_path, address(this), block.timestamp.add(1800));\\n}\\n```\\n\\nThis step can be simplified in two ways.\\nFirst, the `safeApprove` method isn't useful because its usage is not recommended anymore.\\nThe OpenZeppelin version 4 implementation states the method is deprecated and its usage is discouraged.\\n```\\n\\* @dev Deprecated. This function has issues similar to the ones found in\\n\\* {IERC20-approve}, and its usage is discouraged.\\n```\\n\\n```\\n     \\* @dev Deprecated. This function has issues similar to the ones found in\\n     \\* {IERC20-approve}, and its usage is discouraged.\\n```\\n\\nAnother step is swapping the tokens on Uniswap.\\n```\\nIUniswapRouter(SUSHISWAP).swapExactTokensForTokens(\\_badger, uint256(0), \\_path, address(this), block.timestamp.add(1800));\\n```\\n\\nIn this case, the last argument `block.timestamp.add(1800)` is the deadline. This is useful when the transaction is sent to the network and a deadline is needed to expire the transaction. However, the execution is right now and there's no need for a future expiration date.\\nRemoving the safe math addition will have the same end effect, the tokens will be swapped and the call is not at risk to expire.

Impact:

Recommended mitigation:
Resolution\\nComment from NUTS Finance team:\\nWe have replaced all safeApprove() usage with approve() and used block.timestamp as the expiration date.\\nDo not use safe math when sending the expiration date. Use `block.timestamp` for the same effect and a reduced gas cost.\\nApply the same principles for other Single Plus Tokens.