code-423n4 / 2024-04-panoptic-findings

9 stars 4 forks source link

Insolvent users may bypass solvency checks, risking protocol stability and potential financial losses. #405

Closed c4-bot-6 closed 6 months ago

c4-bot-6 commented 6 months ago

Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L932-L946 https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L880-L946

Vulnerability details

Issue Description

_validateSolvency function may fail to accurately determine a user's solvency if the solvency check is bypassed or miscalculated. This function relies on the _checkSolvencyAtTick function to determine the user's solvency at specific ticks.

File: PanopticPool.sol#L933-L945

        // Check the user's solvency at the fast tick; revert if not solvent
        bool solventAtFast = _checkSolvencyAtTick(
            user,
            positionIdList,
            currentTick,
            fastOracleTick,
            buffer
        );
        if (!solventAtFast) revert Errors.NotEnoughCollateral();

        // If one of the ticks is too stale, we fall back to the more conservative tick, i.e, the user must be solvent at both the fast and slow oracle ticks.
        if (Math.abs(int256(fastOracleTick) - slowOracleTick) > MAX_SLOW_FAST_DELTA)
            if (!_checkSolvencyAtTick(user, positionIdList, currentTick, slowOracleTick, buffer))
                revert Errors.NotEnoughCollateral();
    }

If the solvency check is bypassed or miscalculated, the _validateSolvency function may incorrectly determine a user's solvency.

Root Cause

The _validateSolvency function relies on the _checkSolvencyAtTick function to determine a user's solvency at specific ticks (fast and slow oracle ticks).

The solvency check is performed at the fast oracle tick first, and if the delta between the fast and slow oracle ticks exceeds MAX_SLOW_FAST_DELTA, the solvency is also checked at the slow oracle tick.

   bool solventAtFast = _checkSolvencyAtTick(
       user,
       positionIdList,
       currentTick,
       fastOracleTick,
       buffer
   );
   if (!solventAtFast) revert Errors.NotEnoughCollateral();

   if (Math.abs(int256(fastOracleTick) - slowOracleTick) > MAX_SLOW_FAST_DELTA)
       if (!_checkSolvencyAtTick(user, positionIdList, currentTick, slowOracleTick, buffer))
           revert Errors.NotEnoughCollateral();

If the _checkSolvencyAtTick function fails to accurately determine a user's solvency based on their collateral balance and the current market conditions, it could lead to the solvency check being bypassed.

Additionally, if the fast and slow oracle ticks can be manipulated or the MAX_SLOW_FAST_DELTA threshold is set inappropriately, it could allow insolvent users to pass the solvency check.

Impact

Insolvent users may be able to gain an unfair advantage by taking positions they cannot fully collateralize, potentially leading to liquidations and loss of funds for other users.

Proof of Concept

  1. Assume a user, Alice, wants to mint an option position while being insolvent.
  2. Alice's collateral balance is insufficient to cover the collateral requirement for the option position she intends to mint.
  3. Alice calls the mintOptions function in the PanopticPool contract, which internally calls the _validateSolvency function to check her solvency before allowing the mint.
  4. Inside the _validateSolvency function, the following steps occur:
    • The fast oracle tick is computed using PanopticMath.computeMedianObservedPrice.
    • The slow oracle tick is computed based on the SLOW_ORACLE_UNISWAP_MODE flag.
    • The _checkSolvencyAtTick function is called with the fast oracle tick to check Alice's solvency.
  5. Scenario 1: Bypassing the solvency check
    • Assume the _checkSolvencyAtTick function is implemented incorrectly and always returns true, regardless of Alice's actual solvency.
    • In this case, even though Alice is insolvent, the solventAtFast variable will be set to true.
    • The _validateSolvency function will not revert, allowing Alice to proceed with minting the option position.
  6. Scenario 2: Manipulating the oracle ticks
    • Assume the fast and slow oracle ticks can be manipulated by an attacker.
    • The attacker manipulates the fast oracle tick to a value that makes Alice appear solvent, even though she is actually insolvent.
    • If the delta between the manipulated fast oracle tick and the slow oracle tick does not exceed MAX_SLOW_FAST_DELTA, the solvency check at the slow oracle tick will be skipped.
    • As a result, Alice will pass the solvency check and be allowed to mint the option position.
  7. Scenario 3: Inappropriate MAX_SLOW_FAST_DELTA threshold
    • Assume the MAX_SLOW_FAST_DELTA threshold is set to an inappropriately high value.
    • Even if the fast and slow oracle ticks are significantly different and Alice is insolvent, the delta between the ticks may not exceed the high threshold.
    • Consequently, the solvency check at the slow oracle tick will be skipped, allowing Alice to mint the option position while being insolvent.
  8. In all three scenarios, Alice, an insolvent user, is able to mint an option position, violating the expected behavior of the _validateSolvency function.
  9. If Alice's position is later liquidated due to her insolvency, it could lead to financial losses for the protocol and potentially impact other users who may have their positions liquidated as well.

Tools Used

Manual review, VS Code

Recommended Mitigation

The values of the fast and slow oracle ticks should be carefully chosen and validated to prevent manipulation or exploitation.

    // ...

    bool solventAtFast = _checkSolvencyAtTick(
        user,
        positionIdList,
        currentTick,
        fastOracleTick,
        buffer
    );
    if (!solventAtFast) revert Errors.NotEnoughCollateral();

    // Check solvency at the slow tick if the delta exceeds the threshold
    if (Math.abs(int256(fastOracleTick) - slowOracleTick) > MAX_SLOW_FAST_DELTA) {
        bool solventAtSlow = _checkSolvencyAtTick(user, positionIdList, currentTick, slowOracleTick, buffer);
        if (!solventAtSlow) revert Errors.NotEnoughCollateral();
    }

    // Additional checks and validations...

    // ...
}

Assessed type

Invalid Validation

c4-judge commented 6 months ago

Picodes marked the issue as unsatisfactory: Invalid

Picodes commented 6 months ago

AI-generated