In StabilityPool.sol, manager or owner can call liquidateBorrower()function, whereby a user will be liquidated. However, before calculating the user's debt, the reserve state is not updated. This will result the amount approved for Lending Pool to spend on behalf can be less than the actual debt of the user.
Assume this scenario:
Alice borrowed 10,000 crvUSD and was minted 10,000 DebtTokens
A manager decides to liquidate Alice via liquidateBorrower(). At this point, assume the stored usage index = 1.1
liquidateBorrower()Alice's debt is calculated using usage index = 1.1
in code snippet below taken from liquidateBorrower():
In lendingPool.getUserDebt(), it takes user.scaledDebtBalance.rayMul(reserve.usageIndex)
userDebt will be multiplied with usage index again, as lendingPool.getNormalizedDebt()will return the reserve usage index = 1.1
The above (iii) seems to be incorrect, as it is multiplying by usage Index a second time, over-scaling the debt. For the sake of this particular vulnerability, ignore the double multiplication and assume scaledUserDebt == userDebt
Now, the approve happens
scaledUserDebt is approved to be spent by Lending Pool
The state of the reserves is only updated after via lendingPool.updateState()
Assume at the point the reserves are updated after approving scaledUserDebt, the reserve usage index is now 1.4
Now, finalizeLiquidation()is called
finalizeLiquidation()The usage index used to define the amount to be approved is 1.1 in liquidateBorrower(). However, in finalizeLiquidation(), the current usage index = 1.4
Again, the reserve state is updated in updateReserveState()
The calculation of userDebt as seen above will then use usage index = 1.4
After transferring the RAAC NFTs to Stability Pool, Debt Tokens are burned
In debtToken.burn(), the amount of DebtToken burned is the amountpassed, which is userDebtas seen below
in the snippet below, amountis burned and then amount value is returned from debtToken.burn()
Now, crvUSD is being transferred from Stability Pool to R Token address, using amountScaledvalue
Since usage index used to calculate userDebt for approved amount = 1.1 in liquidateBorrower(),and usage index used in finalizeLiquidation()= 1.4, that would mean the transfer above will fail, as the approved amount < amount to be transferred.
If reserve state is not updated before calculating user debt in liquidateBorrower(), the calculation will use an outdated value for the usage index. In finalizeLiquidation(), it instead uses freshest usage index. The amount approved could be more or less than the actual debt of the user. This can result finalizeLiquidation()to revert in the event amount approved is less than the actual debt.
Manual
Ensure reserve state is updated before calculating the amount to be approved for Lending Pool to spend.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.