sherlock-audit / 2023-02-carapace-judging

2 stars 0 forks source link

unforgiven - User fund loss in withdraw() and deposit() of ProtectionPool due to division error #267

Closed github-actions[bot] closed 1 year ago

github-actions[bot] commented 1 year ago

unforgiven

medium

User fund loss in withdraw() and deposit() of ProtectionPool due to division error

Summary

Functions withdraw() and deposit() in the ProtectionPool doesn't revert when calculated _sTokenShares or _underlyingAmountToTransfer amount is 0 and it can cause users to receive no share or underlying token while they pays underlying token or burns shares. code should revert when calculated amounts are zero.

Vulnerability Detail

This is _deposit() and withdraw() code:

  function _deposit(uint256 _underlyingAmount, address _receiver) internal {
    /// Verify that the pool is not in OpenToBuyers phase
    if (poolInfo.currentPhase == ProtectionPoolPhase.OpenToBuyers) {
      revert ProtectionPoolInOpenToBuyersPhase();
    }

    uint256 _sTokenShares = convertToSToken(_underlyingAmount);
    totalSTokenUnderlying += _underlyingAmount;
    _safeMint(_receiver, _sTokenShares);
    poolInfo.underlyingToken.safeTransferFrom(
      msg.sender,
      address(this),
      _underlyingAmount
    );

    /// Verify leverage ratio only when total capital/sTokenUnderlying is higher than minimum capital requirement
    if (_hasMinRequiredCapital()) {
      /// calculate pool's current leverage ratio considering the new deposit
      uint256 _leverageRatio = calculateLeverageRatio();

      if (_leverageRatio > poolInfo.params.leverageRatioCeiling) {
        revert ProtectionPoolLeverageRatioTooHigh(_leverageRatio);
      }
    }

    emit ProtectionSold(_receiver, _underlyingAmount);
  }

    function withdraw(uint256 _sTokenWithdrawalAmount, address _receiver)
    external
    override
    whenPoolIsOpen
    whenNotPaused
    nonReentrant
  {
    /// Step 1: Retrieve withdrawal details for current pool cycle index
    uint256 _currentCycleIndex = poolCycleManager.getCurrentCycleIndex(
      address(this)
    );
    WithdrawalCycleDetail storage withdrawalCycle = withdrawalCycleDetails[
      _currentCycleIndex
    ];

    /// Step 2: Verify withdrawal request exists in this withdrawal cycle for the user
    uint256 _sTokenRequested = withdrawalCycle.withdrawalRequests[msg.sender];
    if (_sTokenRequested == 0) {
      revert NoWithdrawalRequested(msg.sender, _currentCycleIndex);
    }

    /// Step 3: Verify that withdrawal amount is not more than the requested amount.
    if (_sTokenWithdrawalAmount > _sTokenRequested) {
      revert WithdrawalHigherThanRequested(msg.sender, _sTokenRequested);
    }

    /// Step 4: calculate underlying amount to transfer based on sToken withdrawal amount
    uint256 _underlyingAmountToTransfer = convertToUnderlying(
      _sTokenWithdrawalAmount
    );

    /// Step 5: burn sTokens shares.
    /// This step must be done after calculating underlying amount to be transferred
    _burn(msg.sender, _sTokenWithdrawalAmount);

    /// Step 6: Update total sToken underlying amount
    totalSTokenUnderlying -= _underlyingAmountToTransfer;

    /// Step 7: update seller's withdrawal amount and total requested withdrawal amount
    withdrawalCycle.withdrawalRequests[msg.sender] -= _sTokenWithdrawalAmount;
    withdrawalCycle.totalSTokenRequested -= _sTokenWithdrawalAmount;

    /// Step 8: transfer underlying token to receiver
    poolInfo.underlyingToken.safeTransfer(
      _receiver,
      _underlyingAmountToTransfer
    );

    emit WithdrawalMade(msg.sender, _sTokenWithdrawalAmount, _receiver);
  }

As you can see code doesn't revert when calculated amount is zero and user may lose funds slowly because of the division error. contract may burn user shares and transfer no token to users or transfer users underlying token and mint 0 share for them. when code calculates underlying token amount based on share amount it uses convertToUnderlying() in withdraw() function and because of the division error the calculated amount can be zero but code doesn't check that. when code calculates share amount based on underlying token in _deposit(), it uses convertToSToken() and because of the division error code would mint 0 share for user while transferring user funds.

Impact

Users lose funds when they deposit or withdraw small amounts while those small amounts could have sum up and can be used later.

Code Snippet

https://github.com/sherlock-audit/2023-02-carapace/blob/main/contracts/core/pool/ProtectionPool.sol#L253-L259

https://github.com/sherlock-audit/2023-02-carapace/blob/main/contracts/core/pool/ProtectionPool.sol#L1035-L1037

Tool used

Manual Review

Recommendation

revert when calculated amount is zero

clems4ev3r commented 1 year ago

This should not happen on deposit (for more than a few wei). The amount of underlying withdrawn can be zero if there was a locking event, which is part of the normal functioning of the protocol

hrishibhat commented 1 year ago

Closing based on the above comment