code-423n4 / 2023-12-particle-findings

2 stars 1 forks source link

Liquidation eligibility should be checked before decreasing token premiums with liquidation rewards #14

Closed c4-bot-6 closed 10 months ago

c4-bot-6 commented 10 months ago

Lines of code

https://github.com/code-423n4/2023-12-particle/blob/main/contracts/protocol/ParticlePositionManager.sol#L349-L368

Vulnerability details

Impact

The liquidation eligibility check is performed after the premiums provided by the trader are decreased by liquidation rewards. This poses a risk for traders and could lead to a condition where a trader's position unexpectedly becomes eligible for liquidation.

Proof of Concept

It can be observed that currently, the liquidation eligibility check is performed after decreasing closeCache.tokenFromPremium and closeCache.tokenToPremium by liquidateCache.liquidationRewardFrom and liquidateCache.liquidationRewardTo.

https://github.com/code-423n4/2023-12-particle/blob/main/contracts/protocol/ParticlePositionManager.sol#L349-L368

    /// @inheritdoc IParticlePositionManager
    function liquidatePosition(
        DataStruct.ClosePositionParams calldata params,
        address borrower
    ) external override nonReentrant {
        bytes32 lienKey = keccak256(abi.encodePacked(borrower, params.lienId));
        Lien.Info memory lien = liens.getInfo(lienKey);

        // check lien is valid
        if (lien.liquidity == 0) revert Errors.RecordEmpty();

        // local cache to avoid stack too deep
        DataCache.ClosePositionCache memory closeCache;
        DataCache.LiquidatePositionCache memory liquidateCache;

        // get liquidation parameters
        ///@dev calculate premium outside of _closePosition to allow liquidatePosition to take reward from premium
        (
            closeCache.tokenFrom,
            closeCache.tokenTo,
            liquidateCache.tokenFromOwed,
            liquidateCache.tokenToOwed,
            closeCache.tokenFromPremium,
            closeCache.tokenToPremium,
            closeCache.collateralFrom,

        ) = Base.getOwedInfoConverted(
            DataStruct.OwedInfoParams({
                tokenId: lien.tokenId,
                liquidity: lien.liquidity,
                feeGrowthInside0LastX128: lien.feeGrowthInside0LastX128,
                feeGrowthInside1LastX128: lien.feeGrowthInside1LastX128,
                token0PremiumPortion: lien.token0PremiumPortion,
                token1PremiumPortion: lien.token1PremiumPortion
            }),
            lien.zeroForOne
        );

        // calculate liquidation reward
        liquidateCache.liquidationRewardFrom =
            ((closeCache.tokenFromPremium) * LIQUIDATION_REWARD_FACTOR) /
            uint128(Base.BASIS_POINT);
        liquidateCache.liquidationRewardTo =
            ((closeCache.tokenToPremium) * LIQUIDATION_REWARD_FACTOR) /
            uint128(Base.BASIS_POINT);
>>>     closeCache.tokenFromPremium -= liquidateCache.liquidationRewardFrom;
>>>     closeCache.tokenToPremium -= liquidateCache.liquidationRewardTo;

        // check for liquidation condition
        ///@dev the liquidation condition is that
        ///     (EITHER premium is not enough) OR (cutOffTime > startTime AND currentTime > startTime + LOAN_TERM)
>>>     if (
            !((closeCache.tokenFromPremium < liquidateCache.tokenFromOwed ||
                closeCache.tokenToPremium < liquidateCache.tokenToOwed) ||
                (lien.startTime < lps.getRenewalCutoffTime(lien.tokenId) &&
                    lien.startTime + LOAN_TERM < block.timestamp))
        ) {
            revert Errors.LiquidationNotMet();
        }

       ...

With this design, when the protocol decides to increase the LIQUIDATION_REWARD_FACTOR, it could directly impact traders' positions and potentially cause them to become liquidatable.

https://github.com/code-423n4/2023-12-particle/blob/main/contracts/protocol/ParticlePositionManager.sol#L567-L571

    function updateLiquidationRewardFactor(uint128 liquidationRewardFactor) external override onlyOwner {
        if (liquidationRewardFactor > _LIQUIDATION_REWARD_FACTOR_MAX) revert Errors.InvalidValue();
        LIQUIDATION_REWARD_FACTOR = liquidationRewardFactor;
        emit UpdateLiquidationRewardFactor(liquidationRewardFactor);
    }

Tools Used

Manual review

Recommended Mitigation Steps

Perform the check before decreasing closeCache.tokenFromPremium and closeCache.tokenToPremium so that traders' position condition is easier to predict and maintain.

Assessed type

Context

c4-judge commented 10 months ago

0xleastwood marked the issue as primary issue

romeroadrian commented 10 months ago

Duplicate #51

c4-judge commented 10 months ago

0xleastwood marked the issue as duplicate of #51

c4-judge commented 10 months ago

0xleastwood marked the issue as satisfactory