Closed c4-bot-10 closed 7 months ago
0xEVom marked the issue as sufficient quality report
It is not "taken out of anywhere".
Reserves are defined as balance + debt - lent
So in a liquidation with reserves what happens is the following:
kalinbas marked the issue as disagree with severity
kalinbas marked the issue as agree with severity
kalinbas (sponsor) disputed
What is true in the report is the fact that "lastLendExchangeRateX96" is not properly updated (which is a duplicate of issue #206
jhsagd76 marked the issue as duplicate of #206
jhsagd76 marked the issue as satisfactory
jhsagd76 changed the severity to 2 (Med Risk)
jhsagd76 marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L1123-L1141
Vulnerability details
Quoting comment from
V3Vault.sol
on how liquidation work:This part
if position is not valuable enough - missing part is covered by reserves
is not true. When bad loan happen and collateral not enough to repay debt (original borrow + interest).The debt have never been 100% repaid or write off. Exchange rate (borrowIndex,SupplyIndex) stay the same. The pool still consider debt have been fully repaid while no USDC have been deposited to the pool or taken from somewhere to cover entire 100% debt.
Impact
Wrong handling of liquidation of really bad loan. A chunk of debt never repaid and no money taken out of anywhere to compensate for it and no exchange rate have been updated to reflect this.
Causing wrong calculation for global debt, exchange rate. This mean not all future lenders can take out their correct share worth in USDC.
Proof of Concept
With current implementation of
V3Vault.sol
. Here is simplified stuff happen when calculate liquidation of bad loan:When collateral is worth more than debt
Liquidator pay full debt and receive ~110% value of debt in collateral. https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L1103-L1111
When collateral is worth less than debt
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L1112-L1118
Liquidator will pay 90% of NFT value in USDC and get whole NFT positions in return. The rest of debt suppose to be taken from reserve.
Taking money out of reserve function here:
No debt is write off and no money is taken out of reserve
Look at this condition
if (reserveCost > reserves)
this never be true in practice. Here is simplified math on how Reserve is calculatedReserve = (totalDebt + Debt interest) - (totalLend + Lend interest) + USDC cash in pool
Obviously, 1 small bad NFT loan rarely happen when reserve is enough to cover it.
But there is no else-case to handle when reserve is enough to cover bad loan. This causing these problems with calculation:
lastDebtExchangeRateX96
or debt exchange Rate does not change. This mean all debt still consider fully repaid. This include original borrow + interest.lastLendExchangeRateX96
keep the same meaning Protocol expect to have full debt + interest repaid to lenders. But 1 failed loan already happen with no USDC provided. This mean there are less USDC token than lenders expect to take out from the pool.There exist a Protocol Reserve that was not tracked
But there exist a reserve belong to pool/protocol that can be reduced here
We can see this reserve taken from user profit but this reserve value is not tracked.
User receive less profit by reducing
supplyRateX96
. HencelastLendExchangeRateX96
will be smaller than it should be. This mean user receive less profit than they should be.The reduced profit is now part of reserve, belong to the pool, and no one can access this except admin through
withdrawReserves()
function.Because vault does how much
lastLendExchangeRateX96
rate it have already reduced throughreserveFactorX32
, it is impossible to keep track of how much reserve can be reduced.Thats why
_handleReserveLiquidation()
does not remove money from reserve. It is impossible to know how much reserve can be reduced.Tools Used
manual
Recommended Mitigation Steps
To find missing USDC token to repay bad loan.
reserveFactorX32
directly. By tracking how muchlastLendExchangeRateX96
have been reduced byreserveFactorX32
and take it out from the pool. This is unlikely and unpractical.lastLendExchangeRateX96
. Protocol can also reduce global debt directly. Reduce future profit to compensate for bad loan. This mean decreaselastDebtExchangeRateX96
by missing debt amount. Borrower now have to pay less repayment.Assessed type
Math