msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Users withdraw more assets than should when `mintFee` was called long ago
Severity: high

Description:
The amount of LP-tokens to withdraw is calculated at the `GMXWithdraw.withdraw` before the `mintFee` function is called. The `mintFee` function increases the `totalSupply` amount. This way users receive more tokens than should be at the current timestamp. The longer the period since the last `mintFee` was called the more excess tokens the user receives.\\nThe protocol mints vault token shares as management fees to protocol treasury with the `mintFee` function. This increases the `totalSupply` of the shares. The amount of minted fees depends on the time since the last `mintFee` call.\\n```\\n  function mintFee() public {\\n    _mint(_store.treasury, GMXReader.pendingFee(_store));\\n    _store.lastFeeCollected = block.timestamp;\\n  }\\n```\\n\\nWhile withdrawal amount of LP-token can be calculated with outdated totalSupply:\\n```\\n67    _wc.shareRatio = wp.shareAmt\\n68      * SAFE_MULTIPLIER\\n69      / IERC20(address(self.vault)).totalSupply();\\n70    _wc.lpAmt = _wc.shareRatio\\n71      * GMXReader.lpAmt(self)\\n72      / SAFE_MULTIPLIER;\\n\\n101    self.vault.mintFee();\\n```\\n\\nThe `mintFee` is called only after this calculation.

Impact:
Users can receive excess amounts of tokens during withdrawal. Other users and the protocol management lose value of their shares.\\nTools used\\nManual Review

Recommended mitigation:
Users withdraw more assets than should when `mintFee` was called long ago\\nConsider calling the `mintFee` before the `_wc.shareRatio` calculation.
Vulnerability: Inaccurate Fee Due to missing lastFeeCollected Update Before feePerSecond Modification
Severity: medium

Description:
The protocol charges a management fee based on the `feePerSecond` variable, which dictates the rate at which new vault tokens are minted as fees via the `mintFee` function. An administrative function `updateFeePerSecond` allows the owner to alter this fee rate. However, the current implementation does not account for accrued fees before the update, potentially leading to incorrect fee calculation.\\nThe contract's logic fails to account for outstanding fees at the old rate prior to updating the `feePerSecond`. As it stands, the `updateFeePerSecond` function changes the fee rate without triggering a `mintFee`, which would update the `lastFeeCollected` timestamp and mint the correct amount of fees owed up until that point.\\n```\\nfunction updateFeePerSecond(uint256 feePerSecond) external onlyOwner {\\n        _store.feePerSecond = feePerSecond;\\n        emit FeePerSecondUpdated(feePerSecond);\\n    }\\n```\\n\\nScenario Illustration:\\nUser A deposits, triggering `mintFee` and setting `lastFeeCollected` to the current `block.timestamp`.\\nAfter two hours without transactions, no additional `mintFee` calls occur.\\nThe owner invokes `updateFeePerSecond` to increase the fee by 10%.\\nUser B deposits, and `mintFee` now calculates fees since `lastFeeCollected` using the new, higher rate, incorrectly applying it to the period before the rate change.

Impact:
The impact is twofold:\\nAn increased `feePerSecond` results in excessively high fees charged for the period before the update.\\nA decreased `feePerSecond` leads to lower-than-expected fees for the same duration.

Recommended mitigation:
Inaccurate Fee Due to missing lastFeeCollected Update Before feePerSecond Modification\\nEnsure the fees are accurately accounted for at their respective rates by updating `lastFeeCollected` to the current timestamp prior to altering the `feePerSecond`. This can be achieved by invoking `mintFee` within the `updateFeePerSecond` function to settle all pending fees first:\\n```\\nfunction updateFeePerSecond(uint256 feePerSecond) external onlyOwner {\\n  self.vault.mintFee();\\n        _store.feePerSecond = feePerSecond;\\n        emit FeePerSecondUpdated(feePerSecond);\\n    }\\n```\\n
Vulnerability: Missing fees allow cheap griefing attacks that lead to DoS
Severity: medium

Description:
The protocol has chosen a design pattern which does not allow two users at the same time to interact with the system as every time a user deposits or withdraws funds a 2-step process begins which interacts with GMX and only after this process is closed, another user is allowed to start a new process. This design pattern can be abused as griefing attack by front running all user calls with a small deposit, or withdraw call, to DoS the user's call. As the protocol is deployed on L2 blockchains with low transaction fees and does not take fees on depositing or withdrawing funds, this DoS griefing attack is cheap and can be scaled to a point where nobody is able to interact with the system.\\nThe design pattern of the system which leads to this possibility is the status variable.\\nThe flow for such a griefing attack would look like the following:\\nThe system's status is Open\\nUser wants to deposit or withdraw and creates a transaction to do so\\nAttacker front runs the call of the user and deposit or withdraw a small amount of funds (Systems status changes to Deposit or Withdraw)\\nUser's call gets reverted as the check if the system's status is Open reverts\\nDeposit function calls beforeDepositChecks and updates the status to Deposit:\\n```\\nfunction deposit(\\n  GMXTypes.Store storage self,\\n  GMXTypes.DepositParams memory dp,\\n  bool isNative\\n) external {\\n // rest of code\\n GMXChecks.beforeDepositChecks(self, _dc.depositValue);\\n\\n  self.status = GMXTypes.Status.Deposit;\\n // rest of code\\n}\\n```\\n\\nThe beforeDepositChecks function reverts if the current status is not Open:\\n```\\nfunction beforeDepositChecks(\\n  GMXTypes.Store storage self,\\n  uint256 depositValue\\n) external view {\\n  if (self.status != GMXTypes.Status.Open)\\n    revert Errors.NotAllowedInCurrentVaultStatus();\\n // rest of code\\n}\\n```\\n\\nThe same pattern is implemented in the withdraw flow.

Impact:
DoS of the whole system for all depositors.

Recommended mitigation:
Implement fees, for depositing and withdrawing, to increase the costs of such a griefing attack, or rethink the status architecture.
Vulnerability: emergencyResume does not handle the afterDepositCancellation case correctly
Severity: medium

Description:
The `emergencyResume` function is intended to recover the vault's liquidity following an `emergencyPause`. It operates under the assumption of a successful deposit call. However, if the deposit call is cancelled by GMX, the `emergencyResume` function does not account for this scenario, potentially locking funds.\\nWhen `emergencyResume` is invoked, it sets the vault's status to "Resume" and deposits all LP tokens back into the pool. The function is designed to execute when the vault status is "Paused" and can be triggered by an approved keeper.\\n```\\nfunction emergencyResume(\\n    GMXTypes.Store storage self\\n  ) external {\\n    GMXChecks.beforeEmergencyResumeChecks(self);\\n\\n    self.status = GMXTypes.Status.Resume;\\n\\n    self.refundee = payable(msg.sender);\\n\\n    GMXTypes.AddLiquidityParams memory _alp;\\n\\n    _alp.tokenAAmt = self.tokenA.balanceOf(address(this));\\n    _alp.tokenBAmt = self.tokenB.balanceOf(address(this));\\n    _alp.executionFee = msg.value;\\n\\n    GMXManager.addLiquidity(\\n      self,\\n      _alp\\n    );\\n  }\\n```\\n\\nShould the deposit fail, the callback contract's `afterDepositCancellation` is expected to revert, which does not impact the continuation of the GMX execution. After the cancellation occurs, the vault status is "Resume", and the liquidity is not re-added to the pool.\\n```\\nfunction afterDepositCancellation(\\n    bytes32 depositKey,\\n    IDeposit.Props memory /* depositProps */,\\n    IEvent.Props memory /* eventData */\\n  ) external onlyController {\\n    GMXTypes.Store memory _store = vault.store();\\n\\n    if (_store.status == GMXTypes.Status.Deposit) {\\n      if (_store.depositCache.depositKey == depositKey)\\n        vault.processDepositCancellation();\\n    } else if (_store.status == GMXTypes.Status.Rebalance_Add) {\\n      if (_store.rebalanceCache.depositKey == depositKey)\\n        vault.processRebalanceAddCancellation();\\n    } else if (_store.status == GMXTypes.Status.Compound) {\\n      if (_store.compoundCache.depositKey == depositKey)\\n        vault.processCompoundCancellation();\\n    } else {\\n      revert Errors.DepositCancellationCallback();\\n    }\\n  }\\n```\\n\\nGiven this, another attempt to execute `emergencyResume` will fail because the vault status is not "Paused".\\n```\\nfunction beforeEmergencyResumeChecks (\\n    GMXTypes.Store storage self\\n  ) external view {\\n    if (self.status != GMXTypes.Status.Paused)\\n      revert Errors.NotAllowedInCurrentVaultStatus();\\n  }\\n```\\n\\nIn this state, an attempt to revert to "Paused" status via `emergencyPause` could fail in GMXManager.removeLiquidity, as there are no tokens to send back to the GMX pool, leading to a potential fund lock within the contract.

Impact:
The current implementation may result in funds being irretrievably locked within the contract.

Recommended mitigation:
To address this issue, handle the afterDepositCancellation case correctly by allowing emergencyResume to be called again.
Vulnerability: Incorrect depositable shortToken amount calculation in Delta neutral vaults
Severity: medium

Description:
When calculating the maximum possible depositable amount for delta neutral vaults, `_maxTokenBLending` is calculated incorrectly.\\n```\\n    if (self.delta == GMXTypes.Delta.Neutral) {\\n      (uint256 _tokenAWeight, ) = tokenWeights(self);\\n\\n\\n      uint256 _maxTokenALending = convertToUsdValue(\\n        self,\\n        address(self.tokenA),\\n        self.tokenALendingVault.totalAvailableAsset()\\n      ) * SAFE_MULTIPLIER\\n        / (self.leverage * _tokenAWeight / SAFE_MULTIPLIER);\\n\\n\\n      uint256 _maxTokenBLending = convertToUsdValue(\\n        self,\\n        address(self.tokenB),\\n        self.tokenBLendingVault.totalAvailableAsset()\\n      ) * SAFE_MULTIPLIER\\n        / (self.leverage * _tokenAWeight / SAFE_MULTIPLIER)\\n        - 1e18;\\n```\\n\\nhttps://github.com/Cyfrin/2023-10-SteadeFi/blob/0f909e2f0917cb9ad02986f631d622376510abec/contracts/strategy/gmx/GMXReader.sol#L254-L270\\nIf `a` user wants to deposit `v` value to `a` `l` leveraged delta neutral vault with token weights `a` and `b`, the calculation of required lending amount would be as follows:\\n```\\nTotal value to deposit to GMX = lv\\nValue of tokens to short = lva\\nHence this value will be borrowed from the tokenA lending vault\\nRemaining value to borrow (from tokenB lending vault) = lv - lva - v (deposit value provided by user)\\nHence if there is Tb value of tokens in tokenB lending vault, v <= Tb / (l - la - 1)\\n```\\n

Impact:
Deposit attempts can revert even when there is enough tokens to lend causing inefficiency, loss of gas for depositors and deviation from the protocol specification.

Recommended mitigation:
Change the formula to the correct one.\\n```\\ndiff // Remove the line below\\n// Remove the line below\\ngit a/contracts/strategy/gmx/GMXReader.sol b/contracts/strategy/gmx/GMXReader.sol\\nindex 73bb111..ae819c4 100644\\n// Remove the line below\\n// Remove the line below\\n// Remove the line below\\n a/contracts/strategy/gmx/GMXReader.sol\\n// Add the line below\\n// Add the line below\\n// Add the line below\\n b/contracts/strategy/gmx/GMXReader.sol\\n@@ // Remove the line below\\n266,8 // Add the line below\\n266,7 @@ library GMXReader {\\n         address(self.tokenB),\\n         self.tokenBLendingVault.totalAvailableAsset()\\n       ) * SAFE_MULTIPLIER\\n// Remove the line below\\n        / (self.leverage * _tokenAWeight / SAFE_MULTIPLIER)\\n// Remove the line below\\n        // Remove the line below\\n 1e18;\\n// Add the line below\\n        / (self.leverage // Remove the line below\\n (self.leverage *_tokenAWeight / SAFE_MULTIPLIER) // Remove the line below\\n 1e18);\\n \\n       _additionalCapacity = _maxTokenALending > _maxTokenBLending ? _maxTokenBLending : _maxTokenALending;\\n     }\\n```\\n
Vulnerability: GMXOracle.sol#L280: function `getLpTokenAmount` icorrectly assumes that the returned price is in 18 decimal places. But it is 30 decimal places.
Severity: low

Description:
`GMXOracle` oracle has a function getLpTokenAmount which is in scope. This is Used in keeper script to calculate how much LP tokens for given USD value.\\nThis function returns the `lpTokenAmount` with 30 decimal places instead of 18 as the function assumes.\\nLets look at the function getLpTokenAmount\\n```\\n  /**\\n    * @notice Get token A and token B's LP token amount required for a given value\\n    * @param givenValue Given value needed, expressed in 1e30 -------------------------- refer this\\n    * @param marketToken LP token address\\n    * @param indexToken Index token address\\n    * @param longToken Long token address\\n    * @param shortToken Short token address\\n    * @param isDeposit Boolean for deposit or withdrawal\\n    * @param maximize Boolean for minimum or maximum price\\n    * @return lpTokenAmount Amount of LP tokens; expressed in 1e18 --------------> refer this\\n  */\\n  function getLpTokenAmount(\\n    uint256 givenValue,\\n    address marketToken,\\n    address indexToken,\\n    address longToken,\\n    address shortToken,\\n    bool isDeposit,\\n    bool maximize\\n  ) public view returns (uint256) {\\n    uint256 _lpTokenValue = getLpTokenValue(\\n      marketToken,\\n      indexToken,\\n      longToken,\\n      shortToken,\\n      isDeposit,\\n      maximize\\n    );\\n\\n\\n    return givenValue * SAFE_MULTIPLIER / _lpTokenValue;\\n  }\\n```\\n\\nSAFE_MULTIPLIER is in 18 decimal places.\\nThe values returned from the function `_lpTokenValue` is in 18 decimal places. Refer the line\\nSo the final returned value from the function `getLpTokenAmount` is (1e30 * 1e18) / 1e18 = 1e30

Impact:
Overestimating the lpToken amount for the given USD value.

Recommended mitigation:
GMXOracle.sol#L280: function `getLpTokenAmount` icorrectly assumes that the returned price is in 18 decimal places. But it is 30 decimal places.\\nUpdate the function `getLpTokenAmount` as shown below.\\n```\\n  function getLpTokenAmount(\\n    uint256 givenValue,\\n    address marketToken,\\n    address indexToken,\\n    address longToken,\\n    address shortToken,\\n    bool isDeposit,\\n    bool maximize\\n  ) public view returns (uint256) {\\n    uint256 _lpTokenValue = getLpTokenValue(\\n      marketToken,\\n      indexToken,\\n      longToken,\\n      shortToken,\\n      isDeposit,\\n      maximize\\n    );\\n\\n    return givenValue * SAFE_MULTIPLIER / _lpTokenValue; ------ remove\\n\\n    return (givenValue * SAFE_MULTIPLIER) / (_lpTokenValue * 1e12); ---- add\\n   \\n  }\\n```\\n
Vulnerability: `Chainlink.latestRoundData()` may return stale results
Severity: low

Description:
The `_getChainlinkResponse()` function is used to get the price of tokens, the problem is that the function does not check for stale results.\\nThe `ChainlinkOracle._getChainlinkResponse()` function is used to get latest Chainlink response.\\n```\\nfunction _getChainlinkResponse(address _feed) internal view returns (ChainlinkResponse memory) {\\n    ChainlinkResponse memory _chainlinkResponse;\\n\\n    _chainlinkResponse.decimals = AggregatorV3Interface(_feed).decimals();\\n\\n    (\\n      uint80 _latestRoundId,\\n      int256 _latestAnswer,\\n      /* uint256 _startedAt */,\\n      uint256 _latestTimestamp,\\n      /* uint80 _answeredInRound */\\n    ) = AggregatorV3Interface(_feed).latestRoundData();\\n\\n    _chainlinkResponse.roundId = _latestRoundId;\\n    _chainlinkResponse.answer = _latestAnswer;\\n    _chainlinkResponse.timestamp = _latestTimestamp;\\n    _chainlinkResponse.success = true;\\n\\n    return _chainlinkResponse;\\n  }\\n```\\n\\nThe problem is that there is not check for stale data. There are some reasons that the price feed can become stale.

Impact:
Since the token prices are used in many contracts, stale data could be catastrophic for the project.

Recommended mitigation:
Read the updatedAt return value from the `Chainlink.latestRoundData()` function and verify that is not older than than specific time tolerance.\\n```\\nrequire(block.timestamp - udpatedData < toleranceTime, "stale price");\\n```\\n
Vulnerability: The `svTokenValue` function can return overestimated value of each strategy vault share token
Severity: medium

Description:
The `GMXReader.svTokenValue` function can return overestimated value of each strategy vault share token due to outdated `totalSupply`, i.e. without including pending management fees for a long period. This issue can cause the protocol unexpected behavior while keepers provide rebalance and when other protocols receive information about shares value.\\nThe `svTokenValue` function calculates the value of each strategy vault share token with the current amount of `totalSupply`, which may not include pending management fees:\\n```\\n  function svTokenValue(GMXTypes.Store storage self) public view returns (uint256) {\\n    uint256 equityValue_ = equityValue(self);\\n    uint256 totalSupply_ = IERC20(address(self.vault)).totalSupply();\\n    if (equityValue_ == 0 || totalSupply_ == 0) return SAFE_MULTIPLIER;\\n    return equityValue_ * SAFE_MULTIPLIER / totalSupply_;\\n  }\\n```\\n\\nSo the returned share value will be overestimated. The longer the period since the last `mintFee` was called the more overestimated shares value is.

Impact:
The `GMXReader.svTokenValue` function returns an overestimated value of the share token. This issue can cause the protocol unexpected behavior while keepers provide rebalance and when other protocols receive information about the shares value.\\nTools used\\nManual Review

Recommended mitigation:
Consider adding `pendingFee` to the totalSupply:\\n```\\n  function svTokenValue(GMXTypes.Store storage self) public view returns (uint256) {\\n    uint256 equityValue_ = equityValue(self);\\n    uint256 totalSupply_ = IERC20(address(self.vault)).totalSupply();\\n    if (equityValue_ == 0 || totalSupply_ == 0) return SAFE_MULTIPLIER;\\n// Remove the line below\\n    return equityValue_ * SAFE_MULTIPLIER / totalSupply_;\\n// Add the line below\\n    return equityValue_ * SAFE_MULTIPLIER / (totalSupply_ // Add the line below\\n pendingFee(self));\\n  } \\n```\\n
Vulnerability: The `afterWithdrawChecks` applies only if user wants to withdraw in tokenA/B
Severity: high

Description:
The `afterWithdrawChecks` check is very important to be sure that important health parameters are in the proper ranges. But the check is inside brackets of the `if user wants to withdraw in tokenA/B` statement. So if the user wants to withdraw LP-token the check is not provided. This can cause unexpected financial losses.\\nThe `afterWithdrawChecks` check is placed inside the brackets of the if-statement of the `GMXProcessWithdraw.processWithdraw` function. This statement checks `if user wants to withdraw in tokenA/B`. In other cases the `afterWithdrawChecks` check is not provided but should.\\n```\\n 69    // Else if user wants to withdraw in LP token, the tokensToUser is already previously\\n 70    // set in GMXWithdraw.withdraw()\\n 71    if (\\n 72      self.withdrawCache.withdrawParams.token == address(self.tokenA) ||\\n 73      self.withdrawCache.withdrawParams.token == address(self.tokenB)\\n 74    ) {\\n\\n104      GMXChecks.afterWithdrawChecks(self);\\n105    }\\n106  }  \\n```\\n

Impact:
The issue can cause unexpected financial losses.\\nTools used\\nManual Review

Recommended mitigation:
I suppose that the check should be placed after the if statement brackets.
Vulnerability: No check if bridge already exists
Severity: low

Description:
In the current `createBridge` function of the OwnerFacet.sol contract, a critical check to verify if the bridge already exists is missing. This omission can potentially result in double accounting in the yield generation process.\\nIn the rest of the OwnerFacet.sol contract functionality, there are checks in place to prevent the recreation of Vaults or Markets. However, this essential check is absent in the `createBridge()` function. The absence of this check can lead to the unintended creation of duplicate bridges, resulting in double accounting of yield if multiple vaults utilize the same bridge more than once. You can find the missing check in the code here: Link to code.\\nThe potential for double accounting of yield is evident in the following code block:\\n```\\nfunction getZethTotal(uint256 vault) internal view returns (uint256 zethTotal) {\\n    AppStorage storage s = appStorage();\\n    address[] storage bridges = s.vaultBridges[vault];\\n    uint256 bridgeCount = bridges.length;\\n\\n    for (uint256 i; i < bridgeCount;) {\\n        zethTotal += IBridge(bridges[i]).getZethValue(); \\n        unchecked {\\n            ++i;\\n        }\\n    }\\n}\\n```\\n\\nTo demonstrate this behavior, a simple Proof of Concept (PoC) was created. (The test was placed in the Yield.t.sol file.)\\n```\\nfunction test_double_bridge_push() public {\\n    vm.prank(owner);\\n    diamond.createBridge(_bridgeReth, Vault.CARBON, 0, 0);\\n    diamond.getUndistributedYield(Vault.CARBON); \\n    assert(diamond.getUndistributedYield(Vault.CARBON) > 0); // As no yield was generated, this should not be true, but in current situation, it is a proof of double accounting.\\n}\\n```\\n

Impact:
In specific circumstances, if a DAO proposal is confirmed, it could inadvertently trigger the creation of a bridge with the same address for a vault that already uses it. This scenario can lead to double accounting of yield and, as a consequence, potentially expose the protocol to vulnerabilities such as Denial of Service and yield theft.\\nHowever, it's important to note that the likelihood of this issue occurring is relatively low, and the function is governed by the DAO. After discussing this with the sponsor, we have classified this finding as low severity.

Recommended mitigation:
No check if bridge already exists\\nTo address this vulnerability, it is recommended to add the following mitigation to the createBridge function:\\n```\\n// rest of code\\n// Add the line below\\n for (uint i = 0; i < s.vaultBridges[vault].length; i// Add the line below\\n// Add the line below\\n) {\\n// Add the line below\\n     if (s.vaultBridges[vault][i] == bridge) {\\n// Add the line below\\n         revert Errors.BridgeAlreadyExist();\\n// Add the line below\\n     }\\n// Add the line below\\n }\\n```\\n\\nThis change will prevent the inadvertent creation of duplicate bridges and mitigate the risk of double accounting of yield.