Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exchange._liquidate function can cause liquidator to burn too much powerPerp tokens #226

Open
code423n4 opened this issue Mar 20, 2023 · 4 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working H-01 satisfactory satisfies C4 submission criteria; eligible for awards selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-03-polynomial/blob/main/src/Exchange.sol#L333-L353
https://github.com/code-423n4/2023-03-polynomial/blob/main/src/ShortCollateral.sol#L121-L144

Vulnerability details

Impact

When calling the following Exchange._liquidate function, uint256 totalCollateralReturned = shortCollateral.liquidate(positionId, debtRepaying, msg.sender) is executed.

https://github.com/code-423n4/2023-03-polynomial/blob/main/src/Exchange.sol#L333-L353

    function _liquidate(uint256 positionId, uint256 debtRepaying) internal {
        uint256 maxDebtRepayment = shortCollateral.maxLiquidatableDebt(positionId);
        require(maxDebtRepayment > 0);
        if (debtRepaying > maxDebtRepayment) debtRepaying = maxDebtRepayment;

        IShortToken.ShortPosition memory position = shortToken.shortPositions(positionId);

        uint256 totalCollateralReturned = shortCollateral.liquidate(positionId, debtRepaying, msg.sender);

        address user = shortToken.ownerOf(positionId);

        uint256 finalPosition = position.shortAmount - debtRepaying;
        uint256 finalCollateralAmount = position.collateralAmount - totalCollateralReturned;

        shortToken.adjustPosition(positionId, user, position.collateral, finalPosition, finalCollateralAmount);

        pool.liquidate(debtRepaying);
        powerPerp.burn(msg.sender, debtRepaying);
        ...
    }

In the following ShortCollateral.liquidate function, when executing uint256 collateralClaim = debt.mulDivDown(markPrice, collateralPrice), where debt is debtRepaying, collateralClaim can be high if collateralPrice has become much lower comparing to markPrice, such as due to a market sell-off that causes the collateral to be worth much less than before. In this case, totalCollateralReturned can be high as well, which can cause totalCollateralReturned > userCollateral.amount to be true. When such condition is true, totalCollateralReturned = userCollateral.amount is executed, and only userCollateral.amount is transferred to the liquidator after executing ERC20(userCollateral.collateral).safeTransfer(user, totalCollateralReturned).

https://github.com/code-423n4/2023-03-polynomial/blob/main/src/ShortCollateral.sol#L121-L144

    function liquidate(uint256 positionId, uint256 debt, address user)
        external
        override
        onlyExchange
        nonReentrant
        returns (uint256 totalCollateralReturned)
    {
        UserCollateral storage userCollateral = userCollaterals[positionId];

        bytes32 currencyKey = synthetixAdapter.getCurrencyKey(userCollateral.collateral);
        Collateral memory coll = collaterals[currencyKey];

        (uint256 markPrice,) = exchange.getMarkPrice();
        (uint256 collateralPrice,) = synthetixAdapter.getAssetPrice(currencyKey);
        uint256 collateralClaim = debt.mulDivDown(markPrice, collateralPrice);
        uint256 liqBonus = collateralClaim.mulWadDown(coll.liqBonus);
        totalCollateralReturned = liqBonus + collateralClaim;
        if (totalCollateralReturned > userCollateral.amount) totalCollateralReturned = userCollateral.amount;
        userCollateral.amount -= totalCollateralReturned;

        ERC20(userCollateral.collateral).safeTransfer(user, totalCollateralReturned);
        ...
    }

Back in the Exchange._liquidate function, the liquidator burns debtRepaying powerPerp tokens after powerPerp.burn(msg.sender, debtRepaying) is executed. However, in this situation, the liquidator only receives userCollateral.amount collateral tokens that are less than the collateral token amount that should be equivalent to debtRepaying powerPerp tokens but this liquidator still burns debtRepaying powerPerp tokens. As a result, this liquidator loses the extra powerPerp tokens, which are burnt, that are equivalent to the difference between debtRepaying powerPerp tokens' equivalent collateral token amount and userCollateral.amount collateral tokens.

Proof of Concept

The following steps can occur for the described scenario.

  1. Alice calls the Exchange._liquidate function with debtRepaying being 1000e18.
  2. When the ShortCollateral.liquidate function is called, totalCollateralReturned > userCollateral.amount is true, and userCollateral.amount collateral tokens that are equivalent to 500e18 powerPerp tokens are transferred to Alice.
  3. When powerPerp.burn(msg.sender, debtRepaying) is executed in the Exchange._liquidate function, Alice burns 1000e18 powerPerp tokens.
  4. Because Alice only receives userCollateral.amount collateral tokens that are equivalent to 500e18 powerPerp tokens, she loses 500e18 powerPerp tokens.

Tools Used

VSCode

Recommended Mitigation Steps

The Exchange._liquidate function can be updated to burn the number of powerPerp tokens that are equivalent to the actual collateral token amount received by the liquidator instead of burning debtRepaying powerPerp tokens.

@code423n4 code423n4 added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Mar 20, 2023
code423n4 added a commit that referenced this issue Mar 20, 2023
@JustDravee
Copy link

JustDravee commented Mar 24, 2023

Not a dup of #236
Not a dup of #69

@c4-sponsor c4-sponsor added the sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") label Apr 16, 2023
@c4-sponsor
Copy link

mubaris marked the issue as sponsor confirmed

@c4-judge
Copy link
Contributor

c4-judge commented May 2, 2023

JustDravee marked the issue as satisfactory

@c4-judge c4-judge added satisfactory satisfies C4 submission criteria; eligible for awards selected for report This submission will be included/highlighted in the audit report labels May 2, 2023
@c4-judge
Copy link
Contributor

c4-judge commented May 5, 2023

JustDravee marked the issue as selected for report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working H-01 satisfactory satisfies C4 submission criteria; eligible for awards selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
Projects
None yet
Development

No branches or pull requests

5 participants