When the liquidation is profitable, the liquidatorReward is the minimum between assignedCollateral - debtInCollateralToken and a percentage of the future value (5% of future value as specified in Governance Variables & Fees).
This liquidatorReward is then added to debtInCollateralToken, and the result is stored in the liquidatorProfitCollateralToken variable.
This mechanism ensures that the liquidator receives up to a fixed 5% reward on the loan's face value when the liquidation is profitable.
However, when the liquidatorReward is determined in executeLiquidate(), the min() function from the Math library is called with two arguments denominated in different assets:
assignedCollateral - debtInCollateralToken is denominated in ETH
Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT) is denominated in USDC.
As a result, the liquidator will receive 5% of the future value most of the time, since the likelihood of a 6-decimal amount being less than an 18-decimal amount is high.
This liquidatorReward, resulting from the min() call, will be added to the debtInCollateralToken and the result will be stored in the liquidatorProfitCollateralToken variable, which is returned by the executeLiquidate() function.
If 5% of the futureValue in USDC is the minimum compared to assignedCollateral - debtInCollateralToken, an amount in USDC (6 decimals) will be added to an amount in ETH (18 decimals):
liquidatorProfitCollateralToken = debtInCollateralToken e18 + liquidatorReward e6
Proof of Concept
Let's consider the following scenario where 1 ETH = 1 USDC and liquidationRewardPercent = 5%.
Alice wants to lend at 0% and deposits 2000 USDC before calling buyCreditLimit().
Bob wants to borrow 1000 USDC at 0%, he deposits 1510 ETH and calls sellCreditMarket().
The price of ETH drops, now 1 ETH = 0.68 USDC.
The assigned collateral is now worth 1026.8 USDC (1510 * 0.68), making Bob liquidable.
The debt future value in collateral tokens is 1470.588 ETH (1000 / 0.68).
Since assignedCollateral is greater than debtInCollateralToken (1510 > 1470), the following block executes to calculate liquidatorReward and liquidatorProfitCollateralToken:
Lines of code
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Liquidate.sol#L95-L100
Vulnerability details
Impact
When the liquidation is profitable, the
liquidatorReward
is the minimum betweenassignedCollateral - debtInCollateralToken
and a percentage of the future value (5% of future value as specified in Governance Variables & Fees). ThisliquidatorReward
is then added todebtInCollateralToken
, and the result is stored in theliquidatorProfitCollateralToken
variable.This mechanism ensures that the liquidator receives up to a fixed 5% reward on the loan's face value when the liquidation is profitable.
However, when the
liquidatorReward
is determined inexecuteLiquidate()
, themin()
function from theMath
library is called with two arguments denominated in different assets:assignedCollateral - debtInCollateralToken
is denominated in ETHMath.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
is denominated in USDC.As a result, the liquidator will receive 5% of the future value most of the time, since the likelihood of a 6-decimal amount being less than an 18-decimal amount is high.
This
liquidatorReward
, resulting from themin()
call, will be added to thedebtInCollateralToken
and the result will be stored in theliquidatorProfitCollateralToken
variable, which is returned by theexecuteLiquidate()
function.If 5% of the
futureValue
in USDC is the minimum compared toassignedCollateral - debtInCollateralToken
, an amount in USDC (6 decimals) will be added to an amount in ETH (18 decimals):liquidatorProfitCollateralToken = debtInCollateralToken e18 + liquidatorReward e6
Proof of Concept
Let's consider the following scenario where 1 ETH = 1 USDC and liquidationRewardPercent = 5%.
buyCreditLimit()
.sellCreditMarket()
.Since
assignedCollateral
is greater thandebtInCollateralToken
(1510 > 1470), the following block executes to calculateliquidatorReward
andliquidatorProfitCollateralToken
:assignedCollateral - debtInCollateralToken
= 1510e18 - 1470e18 = 40e18However, 50 USDC is worth 73.529 ETH, which is greater than 40 ETH.
liquidatorProfitCollateralToken
is calculated by addingdebtInCollateralToken
(18 decimals) toliquidatorReward
(6 decimals):liquidatorProfitCollateralToken
= 1470.588e18 + 50e6 Instead of:As a result, the liquidator receives a dust amount as a reward.
Tools Used
Manual Review
Recommended Mitigation Steps
Utilize
debtInCollateralToken
(ETH) instead ofdebtPosition.futureValue
(USDC) to compute the 5% liquidation reward inexecuteLiquidate()
:Assessed type
Decimal