msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: LibOracle fails to check the fidelity of price data from WETH/USDC pool, which can lead to price manipulation
Severity: low

Description:
As per the documentation, LibOracle should only be returning the TWAP price from the WETH/USDC pool if the amount of WETH in the pool is >= 100e18. This is to ensure the fidelity of the data, which reduces the risk of price manipulation. However, this is not properly implemented for the case in which there was an invalid fetch of chainlink data. In this case, LibOracle simply returns the TWAP price without checking if there's enough liquidity in the pool. This can lead to a lack of data fidelity for the returned price.\\nIt's clear that reverting should be the correct action rather than returning the TWAP price without checking the liquidity, as even when there is a valid chainlink price, if the TWAP price is closer to the cached price (and there isn't enough liquidity), it will still revert.\\nLibOracle has a `baseOracleCircuitBreaker` function which handles whether to return the TWAP price or the chainlink price, when the asset is USD, and it is defined as follows:\\n```\\nfunction baseOracleCircuitBreaker(\\n    uint256 protocolPrice,\\n    uint80 roundId,\\n    int256 chainlinkPrice,\\n    uint256 timeStamp,\\n    uint256 chainlinkPriceInEth\\n) private view returns (uint256 _protocolPrice) {\\n    bool invalidFetchData = roundId == 0 || timeStamp == 0\\n        || timeStamp > block.timestamp || chainlinkPrice <= 0\\n        || block.timestamp > 2 hours + timeStamp;\\n    // rest of code\\n    if (invalidFetchData || priceDeviation) {\\n        uint256 twapPrice = IDiamond(payable(address(this))).estimateWETHInUSDC(\\n            Constants.UNISWAP_WETH_BASE_AMT, 30 minutes\\n        );\\n        uint256 twapPriceInEther = (twapPrice / Constants.DECIMAL_USDC) * 1 ether;\\n        uint256 twapPriceInv = twapPriceInEther.inv();\\n        if (twapPriceInEther == 0) {\\n            revert Errors.InvalidTwapPrice();\\n        }\\n\\n        if (invalidFetchData) {\\n            return twapPriceInv; // @issue\\n        } else {\\n            // rest of code\\n        }\\n    } else {\\n        return chainlinkPriceInEth;\\n    }\\n}\\n```\\n\\nWhen `invalidFetchData` is true, meaning that the chainlink price was not properly fetched, it will always return `twapPriceInv`. However, this lacks any checks as to whether there is at least 100 WETH in the Uniswap pool, which can result in a lack of data fidelity.

Impact:
When the chainlink oracle is not functioning correctly, LibOracle will always return the TWAP price for the USD asset. However, this lacks any check as to whether there is enough liquidity in the Uniswap pool to guarantee data fidelity, meaning there is a higher likelihood of price manipulation.

Recommended mitigation:
LibOracle fails to check the fidelity of price data from WETH/USDC pool, which can lead to price manipulation\\nBefore returning the TWAP price when `invalidFetchData` is true, first check whether the WETH/USDC pool has enough liquidity.
Vulnerability: Emitting incorrect event value
Severity: low

Description:
The `LibShortRecord::burnNFT()` emits an incorrect event value.\\nThe `burnNFT()` emits an incorrect event value: `nft.owner`. Specifically, the `nft` variable will point to the storage object specified by the `tokenId`. However, the pointing storage object will be deleted before emitting the `Transfer` event.\\nSubsequently, the `ERC721::Transfer` event will be emitted with `nft.owner` == `address(0)`.\\n```\\n    function burnNFT(uint256 tokenId) internal {\\n        //@dev No need to check downcast tokenId because it is handled in function that calls burnNFT\\n        AppStorage storage s = appStorage();\\n      STypes.NFT storage nft = s.nftMapping[tokenId];\\n        if (nft.owner == address(0)) revert Errors.NotMinted();\\n        address asset = s.assetMapping[nft.assetId];\\n        STypes.ShortRecord storage short =\\n            s.shortRecords[asset][nft.owner][nft.shortRecordId];\\n      delete s.nftMapping[tokenId];\\n        delete s.getApproved[tokenId];\\n        delete short.tokenId;\\n      emit Events.Transfer(nft.owner, address(0), tokenId);\\n    }\\n```\\n\\nhttps://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibShortRecord.sol#L366\\nhttps://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibShortRecord.sol#L371\\nhttps://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibShortRecord.sol#L374

Impact:
The `ERC721::Transfer` is an important event. The incorrect event logs may cause off-chain services to malfunction.

Recommended mitigation:
Emit the `Transfer` event before the `delete` operations.\\n```\\n    function burnNFT(uint256 tokenId) internal {\\n        //@dev No need to check downcast tokenId because it is handled in function that calls burnNFT\\n        AppStorage storage s = appStorage();\\n        STypes.NFT storage nft = s.nftMapping[tokenId];\\n        if (nft.owner == address(0)) revert Errors.NotMinted();\\n        address asset = s.assetMapping[nft.assetId];\\n        STypes.ShortRecord storage short =\\n            s.shortRecords[asset][nft.owner][nft.shortRecordId];\\n// Add the line below\\n       emit Events.Transfer(nft.owner, address(0), tokenId);\\n        delete s.nftMapping[tokenId];\\n        delete s.getApproved[tokenId];\\n        delete short.tokenId;\\n// Remove the line below\\n       emit Events.Transfer(nft.owner, address(0), tokenId);\\n    }\\n```\\n
Vulnerability: Blacklisted STADIUM_ADDRESS address cause fund stuck in the contract forever
Severity: medium

Description:
The vulnerability relates to the immutability of `STADIUM_ADDRESS`. If this address is blacklisted by the token used for rewards, the system becomes unable to make transfers, leading to funds being stuck in the contract indefinitely.\\nOwner calls `setContest` with the correct `salt`.\\nThe Organizer sends USDC as rewards to a pre-determined Proxy address.\\n`STADIUM_ADDRESS` is blacklisted by the USDC operator.\\nWhen the contest is closed, the Organizer calls `deployProxyAndDistribute` with the registered `contestId` and `implementation` to deploy a proxy and distribute rewards. However, the call to `Distributor._commissionTransfer` reverts at Line 164 due to the blacklisting.\\nUSDC held at the Proxy contract becomes stuck forever.\\n```\\n// Findings are labeled with '<= FOUND'\\n// File: src/Distributor.sol\\n   function _distribute(address token, address[] memory winners, uint256[] memory percentages, bytes memory data)\\n       // rest of code\\n       _commissionTransfer(erc20);// <= FOUND\\n       // rest of code\\n   }\\n    // rest of code\\n   function _commissionTransfer(IERC20 token) internal {\\n       token.safeTransfer(STADIUM_ADDRESS, token.balanceOf(address(this)));// <= FOUND: Blacklisted STADIUM_ADDRESS address cause fund stuck in the contract forever\\n   }\\n```\\n

Impact:
This vulnerability is marked as High severity because a blacklisted `STADIUM_ADDRESS` would lead to funds being locked in the Proxy address permanently. Funds are already held in the Proxy, and the Proxy's `_implementation` cannot be changed once deployed. Even the `ProxyFactory.distributeByOwner()` function cannot rescue the funds due to the revert.

Recommended mitigation:
It is recommended to allow `STADIUM_ADDRESS` to be updatable by a dedicated admin role to avoid token transfer blacklisting. Moreover, since `STADIUM_ADDRESS` is no longer `immutable`, `storage` collision should be taken into account.
Vulnerability: `InvestorBasedRateLimiter::setInvestorMintLimit` and `setInvestorRedemptionLimit` can make subsequent calls to `checkAndUpdateMintLimit` and `checkAndUpdateRedemptionLimit` revert due to underflow
Severity: low

Description:
`InvestorBasedRateLimiter::_checkAndUpdateRateLimitState` L211-213 subtracts the current mint/redemption amount from the corresponding limit:\\n```\\nif (amount > rateLimit.limit - rateLimit.currentAmount) {\\n  revert RateLimitExceeded();\\n}\\n```\\n\\nIf `setInvestorMintLimit` or `setInvestorRedemptionLimit` are used to set the limit amount for minting or redemptions smaller than the current mint/redemption amount, calls to this function will revert due to underflow.

Impact:
`InvestorBasedRateLimiter::setInvestorMintLimit` and `setInvestorRedemptionLimit` can make subsequent calls to `checkAndUpdateMintLimit` and `checkAndUpdateRedemptionLimit` revert due to underflow.\\nProof of Concept: Add this drop-in PoC to forge-tests/ousg/InvestorBasedRateLimiter/setters.t.sol:\\n```\\nfunction test_setInvestorMintLimit_underflow_DoS() public initDefault(alice) {\\n    // first perform a mint\\n    uint256 mintAmount = rateLimiter.defaultMintLimit();\\n    vm.prank(client);\\n    rateLimiter.checkAndUpdateMintLimit(alice, mintAmount);\\n\\n    // admin now reduces the mint limit to be under the current\\n    // minted amount\\n    uint256 aliceInvestorId = 1;\\n    uint256 newMintLimit = mintAmount - 1;\\n    vm.prank(guardian);\\n    rateLimiter.setInvestorMintLimit(aliceInvestorId, newMintLimit);\\n\\n    // subsequent calls to `checkAndUpdateMintLimit` revert due to underflow\\n    vm.prank(client);\\n    rateLimiter.checkAndUpdateMintLimit(alice, 1);\\n\\n    // same issue affects `setInvestorRedemptionLimit`\\n}\\n```\\n\\nRun with: `forge test --match-test test_setInvestorMintLimit_underflow_DoS`\\nProduces output:\\n```\\nRan 1 test for forge-tests/ousg/InvestorBasedRateLimiter/setters.t.sol:Test_InvestorBasedRateLimiter_setters_ETH\\n[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] test_setInvestorMintLimit_underflow_DoS() (gas: 264384)\\nSuite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.09ms (116.74µs CPU time)\\n```\\n

Recommended mitigation:
Explicitly handle the case where the limit is smaller than the current mint/redemption amount:\\n```\\nif (rateLimit.limit <= rateLimit.currentAmount || amount > rateLimit.limit - rateLimit.currentAmount) {\\n  revert RateLimitExceeded();\\n}\\n```\\n
Vulnerability: Prevent creating an investor record associated with no address
Severity: low

Description:
`InvestorBasedRateLimiter::initializeInvestorStateDefault` is supposed to associate a newly created investor with one or more addresses but the `for` loop which does this can be bypassed by calling the function with an empty array:\\n```\\nfunction initializeInvestorStateDefault(\\n    address[] memory addresses\\n    ) external onlyRole(CONFIGURER_ROLE) {\\n    _initializeInvestorState(\\n      addresses,\\n      defaultMintLimit,\\n      defaultRedemptionLimit,\\n      defaultMintLimitDuration,\\n      defaultRedemptionLimitDuration\\n    );\\n}\\n\\nfunction _initializeInvestorState(\\n    address[] memory addresses,\\n    uint256 mintLimit,\\n    uint256 redemptionLimit,\\n    uint256 mintLimitDuration,\\n    uint256 redemptionLimitDuration\\n    ) internal {\\n    uint256 investorId = ++investorIdCounter;\\n\\n    // @audit this `for` loop can by bypassed by calling\\n    // `initializeInvestorStateDefault` with an empty array\\n    for (uint256 i = 0; i < addresses.length; ++i) {\\n      // Safety check to ensure the address is not already associated with an investor\\n      // before associating it with a new investor\\n      if (addressToInvestorId[addresses[i]] != 0) {\\n        revert AddressAlreadyAssociated();\\n      }\\n      _setAddressToInvestorId(addresses[i], investorId);\\n    }\\n\\n    investorIdToMintState[investorId] = RateLimit({\\n      currentAmount: 0,\\n      limit: mintLimit,\\n      lastResetTime: block.timestamp,\\n      limitDuration: mintLimitDuration\\n    });\\n    investorIdToRedemptionState[investorId] = RateLimit({\\n      currentAmount: 0,\\n      limit: redemptionLimit,\\n      lastResetTime: block.timestamp,\\n      limitDuration: redemptionLimitDuration\\n    });\\n}\\n```\\n

Impact:
An investor record can be created without any associated address. This breaks the following invariant of the `InvestorBasedRateLimiter` contract:\\nwhen a new `investorId` is created, it should be associated with one or more valid addresses\\nProof of Concept: Add this drop-in PoC to forge-tests/ousg/InvestorBasedRateLimiter/setters.t.sol:\\n```\\nfunction test_initializeInvestor_NoAddress() public {\\n    // no investor created\\n    assertEq(0, rateLimiter.investorIdCounter());\\n\\n    // empty input array will bypass the `for` loop that is supposed\\n    // to associate addresses to the newly created investor\\n    address[] memory addresses;\\n\\n    vm.prank(guardian);\\n    rateLimiter.initializeInvestorStateDefault(addresses);\\n\\n    // one investor created\\n    assertEq(1, rateLimiter.investorIdCounter());\\n\\n    // not associated with any addresses\\n    assertEq(0, rateLimiter.investorAddressCount(1));\\n}\\n```\\n\\nRun with: `forge test --match-test test_initializeInvestor_NoAddress`

Recommended mitigation:
In `_initializeInvestorState` revert if the input address array is empty:\\n```\\nuint256 addressesLength = addresses.length;\\n\\nif(addressesLength == 0) revert EmptyAddressArray();\\n```\\n
Vulnerability: `InstantMintTimeBasedRateLimiter::_setInstantMintLimit` and `_setInstantRedemptionLimit` can make subsequent calls to `_checkAndUpdateInstantMintLimit` and `_checkAndUpdateInstantRedemptionLimit` revert due to underflow
Severity: low

Description:
`InstantMintTimeBasedRateLimiter::_checkAndUpdateInstantMintLimit` L103-106 subtracts the currently minted amount from the mint limit:\\n```\\nrequire(\\n  amount <= instantMintLimit - currentInstantMintAmount,\\n  "RateLimit: Mint exceeds rate limit"\\n);\\n```\\n\\nIf `_setInstantMintLimit` is used to set `instantMintLimit < currentInstantMintAmount`, subsequent calls to this function will revert due the underflow. The same is true for `_setInstantRedemptionLimit` and `_checkAndUpdateInstantRedemptionLimit`.

Impact:
`InstantMintTimeBasedRateLimiter::_setInstantMintLimit` and `_setInstantRedemptionLimit` can make subsequent calls to `_checkAndUpdateInstantMintLimit` and `_checkAndUpdateInstantRedemptionLimit` revert due to underflow.

Recommended mitigation:
Explicitly handle the case where the limit is smaller than the current mint/redemption amount:\\n```\\nfunction _checkAndUpdateInstantMintLimit(uint256 amount) internal {\\n    require(\\n      instantMintLimit > currentInstantMintAmount && amount <= instantMintLimit - currentInstantMintAmount,\\n      "RateLimit: Mint exceeds rate limit"\\n    );\\n}\\n\\nfunction _checkAndUpdateInstantRedemptionLimit(uint256 amount) internal {\\n    require(\\n      instantRedemptionLimit > currentInstantRedemptionAmount && amount <= instantRedemptionLimit - currentInstantRedemptionAmount,\\n      "RateLimit: Redemption exceeds rate limit"\\n    );\\n}\\n```\\n
Vulnerability: Protocol may be short-changed by `BuidlRedeemer` during a USDC depeg event
Severity: low

Description:
`OUSGInstantManager::_redeemBUIDL` assumes that 1 BUIDL = 1 USDC as it enforces receiving 1 USDC for every 1 BUIDL it redeems:\\n```\\nuint256 usdcBalanceBefore = usdc.balanceOf(address(this));\\nbuidl.approve(address(buidlRedeemer), buidlAmountToRedeem);\\nbuidlRedeemer.redeem(buidlAmountToRedeem);\\nrequire(\\n  usdc.balanceOf(address(this)) == usdcBalanceBefore + buidlAmountToRedeem,\\n  "OUSGInstantManager::_redeemBUIDL: BUIDL:USDC not 1:1"\\n);\\n```\\n\\nIn the event of a USDC depeg (especially if the depeg is sustained), `BUIDLRedeemer` should return greater than a 1:1 ratio since 1 USDC would not be worth $1, hence 1 BUIDL != 1 USDC meaning the value of the protocol's BUIDL is worth more USDC. However `BUIDLReceiver` does not do this, it only ever returns 1:1.

Impact:
In the event of a USDC depeg the protocol will be short-changed by `BuidlRedeemer` since it will happily receive only 1 USDC for every 1 BUIDL redeemed, even though the value of 1 BUIDL would be greater than the value of 1 USDC due to the USDC depeg.

Recommended mitigation:
To prevent this situation the protocol would need to use an oracle to check whether USDC had depegged and if so, calculate the amount of USDC it should receive in exchange for its BUIDL. If it is short-changed it would either have to revert preventing redemptions or allow the redemption while saving the short-changed amount to storage then implement an off-chain process with BlackRock to receive the short-changed amount.\\nAlternatively the protocol may simply accept this as a risk to the protocol that it will be willingly short-changed during a USDC depeg in order to allow redemptions to continue.
Vulnerability: Consider allowing `ROUSG::burn` to burn dust amounts
Severity: low

Description:
`ROUSG::burn` is used by admins to burn `rOUSG` tokens from any account for regulatory reasons.\\nIt does not allow burning a share amount smaller than 1e4, because this is less than a wei of `OUSG`.\\n```\\nif (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)\\n      revert UnwrapTooSmall();\\n```\\n\\nDepending on the current and future regulatory situation it could be necessary to always be able to burn all shares from users.

Impact:

Recommended mitigation:
Consider allowing the `burn` function to `burn` all remaining shares even if under the minimum amount.
Vulnerability: `Goldilend.lock()` will always revert
Severity: high

Description:
In `lock()`, it calls `_refreshiBGT()` before pulling `iBGT` from the user and will revert while calling `iBGTVault(ibgtVault).stake()`.\\n```\\n  function lock(uint256 amount) external {\\n    uint256 mintAmount = _GiBGTMintAmount(amount);\\n    poolSize += amount;\\n    _refreshiBGT(amount); //@audit should call after depositing funds\\n    SafeTransferLib.safeTransferFrom(ibgt, msg.sender, address(this), amount);\\n    _mint(msg.sender, mintAmount);\\n    emit iBGTLock(msg.sender, amount);\\n  }\\n// rest of code\\n  function _refreshiBGT(uint256 ibgtAmount) internal {\\n    ERC20(ibgt).approve(ibgtVault, ibgtAmount);\\n    iBGTVault(ibgtVault).stake(ibgtAmount); //@audit will revert here\\n  }\\n```\\n

Impact:
Users can't lock `iBGT` as `lock()` always reverts.

Recommended mitigation:
`_refreshiBGT()` should be called after pulling funds from the user.
Vulnerability: Wrong `PoolSize` increment in `Goldilend.repay()`
Severity: high

Description:
When a user repays his loan using `repay()`, it increases `poolSize` with the repaid interest. During the increment, it uses the wrong amount.\\n```\\n  function repay(uint256 repayAmount, uint256 _userLoanId) external {\\n    Loan memory userLoan = loans[msg.sender][_userLoanId];\\n    if(userLoan.borrowedAmount < repayAmount) revert ExcessiveRepay();\\n    if(block.timestamp > userLoan.endDate) revert LoanExpired();\\n    uint256 interestLoanRatio = FixedPointMathLib.divWad(userLoan.interest, userLoan.borrowedAmount);\\n    uint256 interest = FixedPointMathLib.mulWadUp(repayAmount, interestLoanRatio);\\n    outstandingDebt -= repayAmount - interest > outstandingDebt ? outstandingDebt : repayAmount - interest;\\n    loans[msg.sender][_userLoanId].borrowedAmount -= repayAmount;\\n    loans[msg.sender][_userLoanId].interest -= interest;\\n    poolSize += userLoan.interest * (1000 - (multisigShare + apdaoShare)) / 1000; //@audit should use interest instead of userLoan.interest\\n// rest of code\\n  }\\n```\\n\\nIt should use `interest` instead of `userLoan.interest` because the user repaid `interest` only.

Impact:
`poolSize` would be tracked wrongly after calling `repay()` and several functions wouldn't work as expected.

Recommended mitigation:
`poolSize` should be updated using `interest`.
Vulnerability: Users can extend an expired boost using invalidated NFTs.
Severity: high

Description:
In `Goldilend.sol#L251`, a user can extend a boost with invalidated NFTs.\\nThe user has created a boost with a valid NFT.\\nAfter that, the NFT was invalidated using `adjustBoosts()`.\\nAfter the original boost is expired, the user can just call `boost()` with empty arrays, and the boost will be extended again with the original magnitude.\\n```\\n  function _buildBoost(\\n    address[] calldata partnerNFTs,\\n    uint256[] calldata partnerNFTIds\\n  ) internal returns (Boost memory newUserBoost) {\\n    uint256 magnitude;\\n    Boost storage userBoost = boosts[msg.sender];\\n    if(userBoost.expiry == 0) {\\n// rest of code\\n    }\\n    else {\\n      address[] storage nfts = userBoost.partnerNFTs;\\n      uint256[] storage ids = userBoost.partnerNFTIds;\\n      magnitude = userBoost.boostMagnitude; //@audit use old magnitude without checking\\n      for (uint256 i = 0; i < partnerNFTs.length; i++) {\\n        magnitude += partnerNFTBoosts[partnerNFTs[i]];\\n        nfts.push(partnerNFTs[i]);\\n        ids.push(partnerNFTIds[i]);\\n      }\\n      newUserBoost = Boost({\\n        partnerNFTs: nfts,\\n        partnerNFTIds: ids,\\n        expiry: block.timestamp + boostLockDuration,\\n        boostMagnitude: magnitude\\n      });\\n    }\\n  }\\n```\\n

Impact:
Malicious users can use invalidated NFTs to extend their boosts forever.

Recommended mitigation:
Whenever users extend their boosts, their NFTs should be evaluated again.
Vulnerability: Team members can't unstake the initial allocation forever.
Severity: high

Description:
When users call `unstake()`, it calculates the vested amount using `_vestingCheck()`.\\n```\\n  function _vestingCheck(address user, uint256 amount) internal view returns (uint256) {\\n    if(teamAllocations[user] > 0) return 0; //@audit return 0 for team members\\n    uint256 initialAllocation = seedAllocations[user];\\n    if(initialAllocation > 0) {\\n      if(block.timestamp < vestingStart) return 0;\\n      uint256 vestPortion = FixedPointMathLib.divWad(block.timestamp - vestingStart, vestingEnd - vestingStart);\\n      return FixedPointMathLib.mulWad(vestPortion, initialAllocation) - (initialAllocation - stakedLocks[user]);\\n    }\\n    else {\\n      return amount;\\n    }\\n  }\\n```\\n\\nBut it returns 0 for team members and they can't unstake forever. Furthermore, in `stake()`, it just prevents seed investors, not team members. So if team members have staked additionally, they can't unstake also.

Impact:
Team members can't unstake forever.

Recommended mitigation:
`_vestingCheck` should use the same logic as initial investors for team mates.
Vulnerability: In `GovLocks`, it shouldn't use a `deposits` mapping
Severity: high

Description:
In `GovLocks`, it tracks every user's deposit amount using a `deposits` mapping. As users can transfer `govLocks` freely, they might have fewer `deposits` than their `govLocks` balance and wouldn't be able to withdraw when they want.\\n```\\n  function deposit(uint256 amount) external {\\n    deposits[msg.sender] += amount; //@audit no need\\n    _moveDelegates(address(0), delegates[msg.sender], amount);\\n    SafeTransferLib.safeTransferFrom(locks, msg.sender, address(this), amount);\\n    _mint(msg.sender, amount);\\n  }\\n\\n  /// @notice Withdraws Locks to burn Govlocks\\n  /// @param amount Amount of Locks to withdraw\\n  function withdraw(uint256 amount) external {\\n    deposits[msg.sender] -= amount; //@audit no need\\n    _moveDelegates(delegates[msg.sender], address(0), amount);\\n    _burn(msg.sender, amount);\\n    SafeTransferLib.safeTransfer(locks, msg.sender, amount);\\n  }\\n```\\n\\nHere is a possible scenario.\\nAlice has deposited 100 `LOCKS` and got 100 `govLOCKS`. Also `deposits[Alice] = 100`.\\nBob bought 50 `govLOCKS` from Alice to get voting power.\\nWhen Bob tries to call `withdraw()`, it will revert because `deposits[Bob] = 0` although he has 50 `govLOCKS`.

Impact:
Users wouldn't be able to withdraw `LOCKS` with `govLOCKS`.

Recommended mitigation:
We don't need to use the `deposits` mapping at all and we can just rely on `govLocks` balances.