msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: No validation of total supply of unripe beans & Lp in `percentBeansRecapped` & `percentLPRecapped`
Severity: low

Description:
`LibUnripe:percentBeansRecapped` & `LibUnripe:percentLPRecapped` functions calculate the percentage of Unripe Beans and Unripe LPs that have been recapitalized, respectively. These percentages are calculated based on the underlying balance of the Unripe Tokens and their total supply. There is no check if the `totalSupply` is zero which is used as division in the calculation.\\nSee the following code for both the functions:\\n```\\n  /**\\n     * @notice Returns the percentage that Unripe Beans have been recapitalized.\\n     */\\n function percentBeansRecapped() internal view returns (uint256 percent) {\\n        AppStorage storage s = LibAppStorage.diamondStorage();\\n        return s.u[C.UNRIPE_BEAN].balanceOfUnderlying.mul(DECIMALS).div(C.unripeBean().totalSupply());\\n  }\\n\\n  /**\\n     * @notice Returns the percentage that Unripe LP have been recapitalized.\\n     */\\n  function percentLPRecapped() internal view returns (uint256 percent) {\\n        AppStorage storage s = LibAppStorage.diamondStorage();\\n        return C.unripeLPPerDollar().mul(s.recapitalized).div(C.unripeLP().totalSupply());\\n   }\\n```\\n

Impact:
If the `totalSupply` in these two functions becomes zero, the calculation of the percentage of recapitalized Unripe Beans or LP tokens would result in a division by zero error. This is because of the denominator in the calculation. When the total supply is zero, dividing by zero is not defined in Solidity, and the contract would revert with an error.\\nThese functions are used widely across the different contracts at crucial places. So they will effect a lot of functionalities.

Recommended mitigation:
To handle this scenario, appropriate checks should be added to ensure that the `totalSupply` of Unripe Beans or LP tokens is non-zero before performing the division operation.
Vulnerability: `Chainlink` oracle returns stale price due to `CHAINLINK_TIMEOUT` variable in `LibChainlinkOracle` being set to 4 hours
Severity: medium

Description:
The `LibChainlinkOracle` library utilizes a `CHAINLINK_TIMEOUT` constant set to `14400` seconds (4 hours). This duration is four times longer than the `Chainlink` heartbeat that is `3600` seconds (1 hour), potentially introducing a significant delay in recognizing stale or outdated price data.\\nThe `LibChainlinkOracle::checkForInvalidTimestampOrAnswer` function accepts three input arguments: `timestamp`, `answer` and `currentTimestamp` and check if the return `answer` from `Chainlinlink Oracle` or the `timestamp` is invalid:\\n```\\n    function checkForInvalidTimestampOrAnswer(\\n        uint256 timestamp,\\n        int256 answer,\\n        uint256 currentTimestamp\\n    ) private pure returns (bool) {\\n        // Check for an invalid timeStamp that is 0, or in the future\\n        if (timestamp == 0 || timestamp > currentTimestamp) return true;\\n        // Check if Chainlink's price feed has timed out\\n        if (currentTimestamp.sub(timestamp) > CHAINLINK_TIMEOUT) return true;\\n        // Check for non-positive price\\n        if (answer <= 0) return true;\\n    }\\n```\\n\\nThe function also checks if the difference between the `currentTimestamp` and the `timestamp` is greater then `CHAINLINK_TIMEOUT`. The `CHAINLINK_TIMEOUT` is defined to be 4 hours:\\n```\\n uint256 public constant CHAINLINK_TIMEOUT = 14400; // 4 hours: 60 * 60 * 4\\n```\\n

Impact:
The `Chainlink` heartbeat indicates the expected frequency of updates from the oracle. The `Chainlink` heartbeat on Ethereum for `Eth/Usd` is `3600` seconds (1 hour).\\nhttps://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1&search=0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419\\nBut the defined `CHAINLINK_TIMEOUT` in the `LibChainlinkOracle` is `14400` seconds (4 hours).\\nA `CHAINLINK_TIMEOUT` that is significantly longer than the heartbeat can lead to scenarios where the `LibChainlinkOracle` library accepts data that may no longer reflect current market conditions. Also, in volatile markets, a 4-hour window leads to accepting outdated prices, increasing the risk of price slippage.

Recommended mitigation:
Consider reducing the `CHAINLINK_TIMEOUT` to align more closely with the `Chainlink` heartbeat on Ethereum, enhancing the relevance of the price data.
Vulnerability: Accidental `renounceOwnership()` call can disrupt key operations in multiple contracts.
Severity: low

Description:
`Ownable` contains a function named `renounceOwnership()` which can be used to remove the ownership of contracts in a protocol.\\nThis can lead to `SDLPoolCCIPControllerPrimary`, `SDLPoolCCIPControllerPrimary`, `WrappedTokenBridge`, `LinearBoostController` and `RESDLTokenBridge` contracts becoming disowned, which will then break critical functions of the protocol.\\nThe `WrappedTokenBridge`, `LinearBoostController` and `RESDLTokenBridge` contracts inherit from `Ownable`, `SDLPoolCCIPControllerPrimary` from `SDLPoolCCIPController` which inherits `Ownable`, and `SDLPoolCCIPControllerSecondary` inherits from SDLPoolCCIPControllerPrimary; and hence inherit `renounceOwnership()` function.\\nThe owner could accidentally (or intentionally) call `renounceOwnership()` which transfers ownership to `address(0)`. This will break numerous functions within each contract referenced that has the `onlyOwner()` modifier assigned. Below are a list of those functions:\\n`SDLPoolCCIPControllerPrimary`\\n`setRewardsInitiator()`\\n`setWrappedRewardToken()`\\n`approveRewardTokens()`\\n`removeWhitelistedChain()`\\n`addWhitelistedChain()`\\n`SDLPoolCCIPControllerSecondary`\\n`setExtraArgs()`\\n`WrappedTokenBridge`\\n`recoverTokens()`\\n`transferTokens()`\\n`LinearBoostController`\\n`setMaxLockingDuration()`\\n`setMaxBoost()`\\n`RESDLTokenBridge`.\\n`setExtraArgs()`\\nPOC\\nAdd this test to `test/core/ccip/sdl-pool-ccip-controller-primary.test.ts`\\n```\\n it.only('renounce ownership', async () => {\\n    console.log("Owner before", await controller.owner())\\n    // set max link fee\\n    await controller.setMaxLINKFee(toEther(100))\\n    // console out the max link fee\\n    console.log("Set max link fee with onlyOwner modifier", await controller.maxLINKFee())\\n    \\n    // renounce ownership using renounceOwnership() from owner contract\\n    await expect(controller.renounceOwnership())\\n    // set max link fee and expect revert\\n    await expect(controller.setMaxLINKFee(toEther(200))).to.be.revertedWith('Ownable: caller is not the owner')\\n    // console out the max link fee\\n    console.log("set max link fee hasn't changed", await controller.maxLINKFee())\\n    // console out the owner\\n    console.log("Owner after", await controller.owner())\\n \\n  })\\n```\\n

Impact:

Recommended mitigation:
Accidental `renounceOwnership()` call can disrupt key operations in multiple contracts.\\nDisable `renounceOwnership()` if function in the Ownable contract not required.\\n```\\n// Add the line below\\n function renounceOwnership() public override onlyOwner {\\n// Add the line below\\n     revert ("Not allowed");\\n// Add the line below\\n }\\n```\\n
Vulnerability: No way to revoke approval in the SDLPool might lead to unauthorized calling transfer of locks.
Severity: medium

Description:
There is no way to revoke the approval which given via the approvefunction They may able execute transfers even after the owner revokes their permission using the `setApprovalForAll` function.\\nThe `setApprovalForAll` function allows the owner to approve anyone as the operator.\\n```\\n    function setApprovalForAll(address _operator, bool _approved) external {\\n        address owner = msg.sender;\\n        if (owner == _operator) revert ApprovalToCaller();\\n\\n        operatorApprovals[owner][_operator] = _approved;\\n        emit ApprovalForAll(owner, _operator, _approved);\\n    }\\n```\\n\\nIn the same vein, the `approve` function allows the owner or operator to `approve` anyone to transfer the lock.\\n```\\n    function approve(address _to, uint256 _lockId) external {\\n        address owner = ownerOf(_lockId);\\n\\n        if (_to == owner) revert ApprovalToCurrentOwner(); //@note\\n        if (msg.sender != owner && !isApprovedForAll(owner, msg.sender)) revert SenderNotAuthorized();\\n\\n        tokenApprovals[_lockId] = _to;\\n        emit Approval(owner, _to, _lockId);\\n    }\\n```\\n\\nNote that in the function, lock cannot be approved to the owner (but can be approved to any of the operators), and can be called by the owner/operator (see the `isApprovedForAll` modifier).\\nIf the operator approves himself to the lock, using the `approve` function, and later on, his operator status gets revoked, his lock approval status is not cleared, meaning he still has access to the lock.\\nAs an extreme example\\nUser1 owns 5 locks.\\nHe calls the `setApprovalForAll` setting User2 as his operator.\\nUser2 calls the `approve` function on all 5 locks (It succeeds as there's no check preventing this unlike with the lock owner), getting herself both operator approval and token approvals.\\nUser1 revokes User2's operator status.\\nUser2 still has access to the locks and can transfer them.

Impact:
Uncleared approval, gives access to transfer token.

Recommended mitigation:
No way to revoke approval in the SDLPool might lead to unauthorized calling transfer of locks.\\nInclude a check to see if the `_to` in the `approve` function is an operator, revert if it is. Or clear an operator's token approvals after revoking his operator status.
Vulnerability: depositors face immediate loss in case `equity = 0`
Severity: medium

Description:
The vulnerability in the `valueToShares` function exposes users to significant losses in case the equity `(currentAllAssetValue - debtBorrowed)` becomes zero due to strategy losses, users receive disproportionately low shares, and take a loss Immediately.\\nWhen a user deposits to the contract, the calculation of the shares to be minted depends on the `value` of `equity` added to the contract after a successful deposit. In other words:\\n`value` = `equityAfter` - `equityBefore`, while:\\n`equity` = `totalAssetValue` - `totalDebtValue`. and we can see that here :\\n```\\n   function processDeposit(GMXTypes.Store storage self) external {\\n        self.depositCache.healthParams.equityAfter = GMXReader.equityValue(self);\\n        self.depositCache.sharesToUser = GMXReader.valueToShares(\\n            self,\\n            self.depositCache.healthParams.equityAfter - self.depositCache.healthParams.equityBefore,\\n            self.depositCache.healthParams.equityBefore\\n        );\\n\\n        GMXChecks.afterDepositChecks(self);\\n    }\\n    // value to shares function :\\n\\n     function valueToShares(GMXTypes.Store storage self, uint256 value, uint256 currentEquity)\\n        public\\n        view\\n        returns (uint256)\\n    {\\n\\n        uint256 _sharesSupply = IERC20(address(self.vault)).totalSupply() + pendingFee(self); // shares is added\\n        if (_sharesSupply == 0 || currentEquity == 0) return value;\\n        return value * _sharesSupply / currentEquity;\\n    }\\n```\\n\\nNOTICE: When the equity value is `0`, the shares minted to the user equal the deposited value itself. The equity value can become zero due to various factors such as strategy losses or accumulated lending interests... ect\\nIn this scenario, the user immediately incurs a loss, depending on the total supply of `svToken` (shares).\\nConsider the following simplified example:\\nThe total supply of `svToken` is (1,000,000 * 1e18) (indicating users holding these shares).\\nthe equity value drops to zero due to strategy losses and a user deposits 100 USD worth of value,\\nDue to the zero equity value, the user is minted 100 shares (100 * 1e18).\\nConsequently, the value the user owns with these shares immediately reduces to 0.001 USD. `100 * 100 * 1e18 / 1,000,000 = 0.001 USD` (value * equity / totalSupply).\\nIn this case, the user immediately shares their entire deposited value with these old minted shares and loses their deposit, whereas those old shares should be liquidated some how.\\nNotice: If the total supply is higher, the user loses more value, and vice versa.

Impact:
users face immediate loss of funds in case equity drops to zero

Recommended mitigation:
use a liquidation mechanism that burns the shares of all users when equity drops to zero.
Vulnerability: The protocol will mint unnecessary fees if the vault is paused and reopened later.
Severity: medium

Description:
Unnecessary fees will be minted to the treasury if the vault is paused and reopened later.\\nBased on the test results, the protocol mints 5(this can be more) wei(gvToken) for each `gvToken` every second since the last fee collection. For example, if the `totalSupply` of `gvToken` is 1000000e18 and the time difference between the current block and the last fee collection is 10 seconds, the amount of lp tokens minted as a fee will be 50000000 wei in terms of `gvToken`. This is acceptable when the protocol is functioning properly.\\n```\\nfunction pendingFee(GMXTypes.Store storage self) public view returns (uint256) {\\n        uint256 totalSupply_ = IERC20(address(self.vault)).totalSupply();\\n        uint256 _secondsFromLastCollection = block.timestamp - self.lastFeeCollected;\\n        return (totalSupply_ * self.feePerSecond * _secondsFromLastCollection) / SAFE_MULTIPLIER;\\n    }\\n```\\n\\nHowever, if the protocol needs to be paused due to a hack or other issues, and then the vault is reopened, let's say after 1 month of being paused, the time difference from `block.timestamp - _secondsFromLastCollection` will be = 2630000s\\nIf the first user tries to deposit after the vault reopens, the fees charged will be 1000000e18 * 5 * 2630000 / 1e18 = 1315000000000\\nThis is an unnecessary fee generated for the treasury because the vault was paused for a long time, but the fee is still generated without taking that into account. This can result in the treasury consuming a portion of the user shares.

Impact:
This will lead to a loss of user shares for the duration when the vault was not active. The severity of the impact depends on the fee the protocol charges per second, the totalSupply of vault tokens, and the duration of the vault being paused.

Recommended mitigation:
If the vault is being reopened, there should be a function to override the _store.lastFeeCollected = block.timestamp; with block.timestamp again.
Vulnerability: Setter functions for core GMX contracts
Severity: medium

Description:
GMX docs state that their `ExchangeRouter` and `GMXOracle` contracts `will` change as new logic is added. Therefore setter functions should be added to `GMXVault.sol` to be able to update the state variables storing those addressed when the need arises.\\nFrom the GMX docs:\\n```\\nIf using contracts such as the ExchangeRouter, Oracle or Reader do note that their addresses will change as new logic is added\\n```\\n

Impact:
Not being able to use the `ExchangeRouter` and `GMXOracle` contracts the protocol would effectively be unusable given their importance.

Recommended mitigation:
Create setter functions in `GMXVault.sol` as below:\\n```\\n  function updateExchangeRouter(address exchangeRouter) external onlyOwner {\\n    _store.exchangeRouter = exchangeRouter;\\n    emit ExchangeRouterUpdated(exchangeRouter);\\n  }\\n\\n  function updateGMXOracle(address gmxOracle) external onlyOwner {\\n    _store.gmxOracle = gmxOracle;\\n    emit GMXOracleUpdated(gmxOracle);\\n  }\\n```\\n
Vulnerability: Wrong errors are used for reverts
Severity: low

Description:
There are checks that revert with wrong errors\\nReverts:\\nhttps://github.com/Cyfrin/2023-10-SteadeFi/blob/0f909e2f0917cb9ad02986f631d622376510abec/contracts/strategy/gmx/GMXChecks.sol#L68-L69\\nhttps://github.com/Cyfrin/2023-10-SteadeFi/blob/0f909e2f0917cb9ad02986f631d622376510abec/contracts/strategy/gmx/GMXChecks.sol#L74-L75\\nhttps://github.com/Cyfrin/2023-10-SteadeFi/blob/0f909e2f0917cb9ad02986f631d622376510abec/contracts/strategy/gmx/GMXChecks.sol#L351-L352\\n```\\nFile: contracts/strategy/gmx/GMXChecks.sol\\n\\n// Should be Errors.EmptyDepositAmount\\nif (self.depositCache.depositParams.amt == 0)\\n      revert Errors.InsufficientDepositAmount();\\n\\n// Should be Errors.EmptyDepositAmount\\nif (depositValue == 0)\\n      revert Errors.InsufficientDepositAmount();\\n\\n// Should be Errors.EmptyDepositAmount\\nif (self.compoundCache.depositValue == 0)\\n      revert Errors.InsufficientDepositAmount();\\n```\\n

Impact:
This can lead to user confusion as they won't receive the accurate revert reason.

Recommended mitigation:
Wrong errors are used for reverts\\nConsider using `Errors.EmptyDepositAmount` for the provided cases.
Vulnerability: Transfer Limit of UNI Tokens May Lead to a DoS and Token Loss Risk
Severity: low

Description:
Users who accumulate more than 2^96 UNI tokens may lose their tokens because transfers above that will always revert.\\nThe UNI token contract imposes a transfer limit, restricting the maximum amount of tokens that can be transferred in a single transaction to 2^96 UNI tokens. Any transfer exceeding this threshold will trigger a transaction revert. The contract relies on the `balanceOf` function to verify the sender's token balance before proceeding with a transfer.\\n```\\n      self.tokenA.safeTransfer(self.withdrawCache.user, self.tokenA.balanceOf(address(this)));\\n```\\n\\nsuch a transfer will always revert for balances above 2^96 UNI tokens\\nhttps://github.com/d-xo/weird-erc20#revert-on-large-approvals--transfers

Impact:
Users who accumulate more than 2^96 UNI tokens may lose their tokens due to a DOS revert when attempting to withdraw their token balance.

Recommended mitigation:
Transfer Limit of UNI Tokens May Lead to a DoS and Token Loss Risk\\nContracts should always check the amount of UNI being transferred before processing the transaction.
Vulnerability: re-entrency possible on processWithdraw since external call is made before burning user's shares in Vault
Severity: medium

Description:
re-entrency possible on processWithdraw since external call is made before burning user's shares in Vault\\n```\\n      if (self.withdrawCache.withdrawParams.token == address(self.WNT)) {\\n        self.WNT.withdraw(self.withdrawCache.tokensToUser);\\naudit transfer ETH and call        (bool success, ) = self.withdrawCache.user.call{value: address(this).balance}("");\\n        require(success, "Transfer failed.");\\n      } else {\\n        // Transfer requested withdraw asset to user\\n        IERC20(self.withdrawCache.withdrawParams.token).safeTransfer(\\n          self.withdrawCache.user,\\n          self.withdrawCache.tokensToUser\\n        );\\n      }\\n\\n      // Transfer any remaining tokenA/B that was unused (due to slippage) to user as well\\n      self.tokenA.safeTransfer(self.withdrawCache.user, self.tokenA.balanceOf(address(this)));\\n      self.tokenB.safeTransfer(self.withdrawCache.user, self.tokenB.balanceOf(address(this)));\\n\\n      // Burn user shares\\n burn is after      self.vault.burn(self.withdrawCache.user, self.withdrawCache.withdrawParams.shareAmt);\\n```\\n\\nhttps://github.com/Cyfrin/2023-10-SteadeFi/blob/main/contracts/strategy/gmx/GMXWithdraw.sol#L182-L197\\nSince the function is only accessible by keeper (likely a router), which from the example of the mockRouter, would bundle the withdraw and "afterWithdrawalExecution" together. However since the router is out-of-scope, and there is still a possible chance that the user can make use of the router to re-enter into the function (without re-entrency lock), and be able to drain more fund that he actually deserves. This is submitted as a medium risk.

Impact:
drain of user funds.

Recommended mitigation:
burn user's share first, before executing external call at the end.
Vulnerability: min max price on getMarketTokenPrice is not utilized such that deposit and withdrawal can use the same price, leading to free tx for cost-free manipulation
Severity: medium

Description:
min max price on getMarketTokenPrice is not utilized such that deposit and withdrawal can use the same price, leading to free tx for cost-free manipulation\\nGMX provides getMarketTokenPrice on its synethicReader which leverages MarketUtils. It allows passing in index/long/short token price with min/max. The isDeposit flag would then be used to determine whether the min or max price would be used for calculating marketTokenPrice, this is important to always favor the protocol and prevent MEV.\\nHowever on the getMarketTokenInfo implemented in GMXOracle, it passes in the same price from the oracle to the min/max price for all long&short/lpToken. This implies the same pricing is used for both deposit and withdrawal, enabling user to freely deposit/withdraw without cost or slippage. Malicious users can use this to trigger rebalance, and hence deposit or withdrawal directly on GMX that benefit the attacker with the use of bundled tx.\\n```\\n    function getMarketTokenPrice(\\n        DataStore dataStore,\\n        Market.Props memory market,\\n        Price.Props memory indexTokenPrice,\\n        Price.Props memory longTokenPrice,\\n        Price.Props memory shortTokenPrice,\\n        bytes32 pnlFactorType,\\n        bool maximize\\n    ) external view returns (int256, MarketPoolValueInfo.Props memory) {\\n        return\\n            MarketUtils.getMarketTokenPrice(\\n                dataStore,\\n                market,\\n                indexTokenPrice,\\n                longTokenPrice,\\n                shortTokenPrice,\\n                pnlFactorType,\\n                maximize\\n            );\\n    }\\n```\\n\\nhttps://github.com/gmx-io/gmx-synthetics/blob/613c72003eafe21f8f80ea951efd14e366fe3a31/contracts/reader/Reader.sol#L187-L206

Impact:
free deposit and withdrawal due to the same token price is used for min or max price, which leading to the same marketTokenPrice calculation for deposit and withdrawal.

Recommended mitigation:
consider adding a small fee(5bps) to buffer the price returned from `_getTokenPriceMinMaxFormatted` on both sides.
Vulnerability: Chainlinks oracle feeds are not immutable
Severity: medium

Description:
That a chainlink oracle works does not mean it will be supported by chainlink in the future and keeps working, and it could also be possible that the address of the price feed changes. Therefore, it does not make sense to prevent price feed addresses from being updated, or removed, but the protocol prevents that.\\nThere is only one function inside ChainlinkARBOracle to update the price feed addresses:\\n```\\nfunction addTokenPriceFeed(address token, address feed) external onlyOwner {\\n  if (token == address(0)) revert Errors.ZeroAddressNotAllowed();\\n  if (feed == address(0)) revert Errors.ZeroAddressNotAllowed();\\n  if (feeds[token] != address(0)) revert Errors.TokenPriceFeedAlreadySet();\\n\\n  feeds[token] = feed;\\n}\\n```\\n\\nAs we can see it will only allow to set the price feed ones and revert if trying to update, or remove a price feed. Therefore, if chainlink changes something, or the owner accidentally set the wrong address, or the protocol no longer wants to support a price feed, it can not be removed, or updated.

Impact:
It is not possible to remove price feeds which are no longer supported by chainlink, or update the addresses of price feeds. This can lead to a complete DoS of the underlying token.\\nAs this feeds mapping is also the only check if it is a valid token when calling the oracle and the feed can not be removed, it will always pass this check even if the protocol no longer wishes to support this token:\\n```\\nfunction consult(address token) public view whenNotPaused returns (int256, uint8) {\\n  address _feed = feeds[token];\\n\\n  if (_feed == address(0)) revert Errors.NoTokenPriceFeedAvailable();\\n // rest of code\\n}\\n```\\n

Recommended mitigation:
Chainlinks oracle feeds are not immutable\\nRemove this line:\\n```\\nif (feeds[token] != address(0)) revert Errors.TokenPriceFeedAlreadySet();\\n```\\n