Open c4-bot-1 opened 5 months ago
GalloDaSballo marked the issue as primary issue
GalloDaSballo marked the issue as sufficient quality report
Instead of wasting gas by frontrunning a newbie liquidator they could also make money and liquidate themselves. On Arbitrum its not possible at all. And if liquidators dont use tools like flashbots or eden its their own fault. No reason to do anything here imo. Dismissed
Whether the option for liquidators to bypass hacks through private mempools is grounds for reducing severity of their abuse is not a trivial question and would best be standardized in the Supreme Court.
I'm taking a stance that this cheap hack is annoying enough for liquidators to be worth Medium severity. Note that if private mempool is in-scope, attacker can use it as well and insert the annoying TX at start of block.
trust1995 marked the issue as selected for report
For transparency and per conversation with the sponsors, see here for the Wise Lending team's mitigation.
Mentioned issue does not seems to be in interest of the borrower, since borrower is more incentivized to perform self-liquidation to earn than preventing others, which becomes a race on who will liquidate first making liquidation a more desired outcome for both parties, no intention to gate the minimum payback threshold by admin.
Lines of code
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurityHelper.sol#L895-L899
Vulnerability details
Impact
Liquidations can be DoSed which increments the risk of bad debt being generated on position.
Proof of Concept
When a liquidator is liquidating a position in WiseLending, the liquidator needs to specify the amount of shares to be repaid. The liquidation logic checks if the positions are indeed liquidable, if so, it validates if the number of shares to be liquidated exceeds the total amount of shares that can be liquidated by using the
WiseSecurityHelper.checkMaxShares() function
. If the amount of shares the liquidator intends to liquidate exceeds the maximum amount of liquidable shares, the execution is revertedThe problem with this approach is that borrowers can frontrun the liquidation and repay as little as 1 share of the existing debt on the same pool that the liquidator decided to liquidate the debt from. This will cause when the liquidation is executed, the total borrow shares of the user on the pool being liquidated to be lower. If the liquidator was trying to liquidate the maximum possible amount of shares, now, the maxShares that can be liquidated will be slightly less than the amount of shares that the liquidator specified, which will cause the tx to revert.
For example, if there is a position in a liquidable state that has 100 borrowShares on the PoolA, and, a liquidator decides to liquidate the maximum possible amount of shares from this position it will send a tx to liquidate 50 shares from that position on the PoolA. The position's owner can use the
WiseLending.paybackExactShares() function
to repay 1 share and frontrun the liquidator's tx. Now, when the liquidator's tx is executed, the position is still liquidable, but, it only has 99 borrowShares on the PoolA, as a result of this, theWiseSecurityHelper.checkMaxShares() function
will determine that the maximum possible amount of liquidable shares is 49.5, and, because the liquidator specified that he intended to liquidate 50 shares, the tx will be reverted.Tools Used
Manual Audit
Recommended Mitigation Steps
In the
WiseSecurityHelper.checkMaxShares() function
, if the_shareAmountToPay
exceedsmaxShares
, don't revert, re-adjust the number of shares that can be liquidated. Return the final value of_shareAmountToPay
and forward it back to theWiseLending.liquidatePartiallyFromTokens() function
. Then, use the final value ofshareAmountToPay
to compute the exact amount of tokens to be repaid in theWiseLending.liquidatePartiallyFromTokens() function
.return;
return _shareAmountToPay;
} else {
return maxShares;
}
revert TooManyShares(); }
function liquidatePartiallyFromTokens( ... uint256 _shareAmountToPay ) ... { ...
data.shareAmountToPay = _shareAmountToPay;
...
//@audit-info => First, determine the maximum amount of liquidable shares
data.shareAmountToPay = WISE_SECURITY.checksLiquidation(
_nftId,
_paybackToken,
_shareAmountToPay
);
//@audit-info => Then, compute the exact amount of tokens required to liquidate the final amount of liquidable shares data.paybackAmount = paybackAmount( _paybackToken,
_shareAmountToPay
data.shareAmountToPay );
...
WISE_SECURITY.checksLiquidation(
_nftId,
_paybackToken,
_shareAmountToPay
);
... }
Assessed type
Context