sherlock-audit / 2023-12-arcadia-judging

19 stars 15 forks source link

NentoR - Optimistically paid initiation rewards may not get covered when liquidations settle through unhappy flow #148

Closed sherlock-admin closed 9 months ago

sherlock-admin commented 9 months ago

NentoR

medium

Optimistically paid initiation rewards may not get covered when liquidations settle through unhappy flow

Summary

Liquidation initiators are sent rewards when they initiate auctions for liquidatable accounts. The problem is that rewards are paid optimistically and there's no guarantee if the accounts will be able to cover them if settled through the unhappy flow.

Vulnerability Detail

Anyone who sees that an account has become liquidatable can initiate a liquidation through Liquidator::liquidateAccount() and immediately get a reward for it.

The flow is Liquidator::liquidateAccount() => AccountV1::startLiquidation() => LendingPool::startLiquidation(). Here's the code for the last one:

function startLiquidation(address initiator, uint256 minimumMargin_)
    external
    override
    whenLiquidationNotPaused
    processInterests
    returns (uint256 startDebt)
{
    // Only Accounts can have debt, and debtTokens are non-transferrable.
    // Hence by checking that the balance of the msg.sender is not 0,
    // we know that the sender is indeed an Account and has debt.
    startDebt = maxWithdraw(msg.sender);
    if (startDebt == 0) revert LendingPoolErrors.IsNotAnAccountWithDebt();

    // Calculate liquidation incentives which have to be paid by the Account owner and are minted
    // as extra debt to the Account.
    (uint256 initiationReward, uint256 terminationReward, uint256 liquidationPenalty) =
        _calculateRewards(startDebt, minimumMargin_);

    // Mint the liquidation incentives as extra debt towards the Account.
    _deposit(initiationReward + liquidationPenalty + terminationReward, msg.sender);

    // Increase the realised liquidity for the initiator.
    // The other incentives will only be added as realised liquidity for the respective actors
    // after the auction is finished.
    realisedLiquidityOf[initiator] += initiationReward;
    totalRealisedLiquidity = SafeCastLib.safeCastTo128(totalRealisedLiquidity + initiationReward);

    // ...
}

The initiator is given a reward for initiating the auction and this reward can be withdrawn immediately afterwards. The issue here is that this is being optimistically paid out to the initiator. There's no guarantee that the auction bids will be able to cover the initiation reward, which will incur losses on the pool. The scenario is only possible to occur in the unhappy flow, which happens when the bidders have failed to pay off the account's outstanding debt in the given cut-off period.

Impact

Losses for the tranches and LPs' when the liquidation is not able to cover the amount of the initiation reward.

Code Snippet

https://github.com/sherlock-audit/2023-12-arcadia/blob/main/lending-v2/src/LendingPool.sol#L861-L886 https://github.com/sherlock-audit/2023-12-arcadia/blob/main/lending-v2/src/LendingPool.sol#L983-L1023

Tool used

Manual Review

Recommendation

Refactor the code so paying out the initiation rewards happens when auctions have been settled (either through the happy or unhappy flow). For the unhappy flow, it should be handled similarly to how liquidationPenalty is currently being handled:

if (openDebt >= liquidationPenalty) {
    // "openDebt" is bigger than the "liquidationPenalty" but smaller than the total pending liquidation incentives.
    // Don't pay out the "liquidationPenalty" to Lps, partially pay out the "terminator".
    realisedLiquidityOf[terminator] += remainder;
}

The same logic should be used for the initiationReward once it's moved there.

Duplicate of #1

sherlock-admin2 commented 9 months ago

1 comment(s) were left on this issue during the judging contest.

takarez commented:

invalid

nevillehuang commented 9 months ago

Similar to #139