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

3 stars 3 forks source link

Liquidations can possibly be prevented if a liquidate call frontruns another one with a partial liquidation #43

Open c4-bot-7 opened 8 months ago

c4-bot-7 commented 8 months ago

Lines of code

https://github.com/code-423n4/2023-12-initcapital/blob/main/contracts/core/InitCore.sol#L538 https://github.com/code-423n4/2023-12-initcapital/blob/main/contracts/core/InitCore.sol#L306 https://github.com/code-423n4/2023-12-initcapital/blob/main/contracts/core/InitCore.sol#L348

Vulnerability details

Impact

User may avoid liquidations by doing smaller ones with less shares, frontrunning bigger ones. It may also happen as a natural flow, being costly for the liquidators as the revert of the transaction happens at the end of the call.

Proof of Concept

liquidate() reverts if the shares out are less than the min shares amount. However, it also limits the shares to liquidate to the debt shares of the user. However, it does not limit the min shares out argument, which means that this argument could make no sense compared to the shares repayed.

Thus, users can liquidate a smaller amount, frontrunning other liquidations and keeping most of the funds. If it happens naturally, the frontrunned liquidators incur gas costs due to the revert happening later in the call.

Here is a POC, place in TestInitCore:


    function test_POC_Liquidate_reverts_frontrunning_minShares() public {
        address poolUSDT = address(lendingPools[USDT]);
        address poolWBTC = address(lendingPools[WBTC]);
        _setTargetHealthAfterLiquidation_e18(1, type(uint64).max); // by pass max health after liquidate capped
        _setFixedRateIRM(poolWBTC, 0.1e18); // 10% per sec

        uint collAmt;
        uint borrAmt;

        {
            uint collUSD = 100_000;
            uint borrUSDMax = 80_000;
            collAmt = _priceToTokenAmt(USDT, collUSD);
            borrAmt = _priceToTokenAmt(WBTC, borrUSDMax);
        }

        address liquidator = BOB;
        deal(USDT, ALICE, collAmt);
        deal(WBTC, liquidator, borrAmt * 2);

        // provides liquidity for borrow
        _fundPool(poolWBTC, borrAmt);

        // create position and collateralize
        uint posId = _createPos(ALICE, ALICE, 1);
        _collateralizePosition(ALICE, posId, poolUSDT, collAmt, bytes(''));

        // borrow
        _borrow(ALICE, posId, poolWBTC, borrAmt, bytes(''));

        // fast forward time and accrue interest
        vm.warp(block.timestamp + 1 seconds);
        ILendingPool(poolWBTC).accrueInterest();

        uint debtShares = positionManager.getPosDebtShares(posId, poolWBTC);

        vm.startPrank(liquidator);
        IERC20(ILendingPool(poolWBTC).underlyingToken()).approve(address(initCore), type(uint).max);
        initCore.liquidate(posId, poolWBTC, debtShares/2, poolUSDT, 0);

        vm.expectRevert();
        initCore.liquidate(posId, poolWBTC, debtShares, poolUSDT, 7953673978200000000*9/10);
        vm.stopPrank();
    }
``

## Tools Used
Vscode, Foundry

## Recommended Mitigation Steps
Limit the min shares out pro-rata to how much the repayed shares were limited.

## Assessed type

Under/Overflow
c4-judge commented 8 months ago

hansfriese marked the issue as primary issue

c4-sponsor commented 8 months ago

fez-init marked the issue as disagree with severity

fez-init commented 8 months ago

This should be QA.

c4-sponsor commented 8 months ago

fez-init (sponsor) acknowledged

hansfriese commented 8 months ago

Downgrade to QA due to the low impact.

c4-judge commented 8 months ago

hansfriese changed the severity to QA (Quality Assurance)

c4-judge commented 8 months ago

hansfriese marked the issue as grade-a