Summary
An underflow in cds profits computation can cause liquidation type 1 to revert.
Root Cause
Here is the formula for cds profits computation. It is possible that returnToTreasury (aka as borrowerDebt) can be greater than deposited amount value (((depositDetail.depositedAmountInETH * depositDetail.ethPriceAtDeposit) / BorrowLib.USDA_PRECISION) / 100) which can cause the formula to revert and DOS the liquidation type 1 process.
File: borrowLiquidation.sol
211: // Calculate the CDS profits, USDA precision is 1e12, @audit need to double check if profit formula is right logic or can underflow
212: uint128 cdsProfits = (((depositDetail.depositedAmountInETH * depositDetail.ethPriceAtDeposit) / BorrowLib.USDA_PRECISION) / 100) - returnToTreasury - returnToAbond;Here is the details of returntoTreasury which is equivalent to borrower's debt amount during time of liquidation. This includes interest accumulated from loan creation up to liquidation time. If lastCumulativeRate reach a certain percentage like 126% in my example issue, borrowerDebt will become greater than deposited amount value ((depositDetail.depositedAmountInETH * depositDetail.ethPriceAtDeposit) / BorrowLib.USDA_PRECISION) / 100) which can cause underflow error.
File: borrowLiquidation.sol
204: // Calculate borrower's debt, rate precision is 1e27, this includes interest accumulated from creation of loan to liquidation
205: uint256 borrowerDebt = ((depositDetail.normalizedAmount * lastCumulativeRate) / BorrowLib.RATE_PRECISION);
206: uint128 returnToTreasury = uint128(borrowerDebt);Here is the details of returnToAbond which represents 10% of deposited amount after subtracting returntotreasury.
File: borrowLiquidation.sol
209: uint128 returnToAbond = BorrowLib.calculateReturnToAbond(depositDetail.depositedAmountInETH, depositDetail.ethPriceAtDeposit, returnToTreasury);File: BorrowLib.sol
137: function calculateReturnToAbond(
138: uint128 depositedAmount,
139: uint128 depositEthPrice,
140: uint128 returnToTreasury
141: ) public pure returns (uint128) {
142: // 10% of the remaining amount, USDA precision is 1e12
143: return (((((depositedAmount * depositEthPrice) / USDA_PRECISION) / 100) - returnToTreasury) * 10) / 100;
144: }Regarding the lastcumulativerate on how it exactly increases, it can be computed by the following below. In line 245, there is _rpow computation in which time interval, interest rate per second are factors in increasing the last cumulative rate. The more time the loan is not liquidated or not paid, the more the loan get bigger due to interest rate which will eventually reach 126%. The cumulative rate variable should start or set at around 100% based on set APR after protocol deployed the borrowing contract, so the very first loan will use the cumulative rate at this rate, then will increase from there moving forward.
File: BorrowLib.sol
222: /**
223: * @dev calculates cumulative rate
224: * @param noOfBorrowers total number of borrowers in the protocol
225: * @param ratePerSec interest rate per second
226: * @param lastEventTime last event timestamp
227: * @param lastCumulativeRate previous cumulative rate
228: */
229: function calculateCumulativeRate(
230: uint128 noOfBorrowers,
231: uint256 ratePerSec,
232: uint128 lastEventTime,
233: uint256 lastCumulativeRate
234: ) public view returns (uint256) {
235: uint256 currentCumulativeRate;
236:
237: // If there is no borrowers in the protocol
238: if (noOfBorrowers == 0) {
239: // current cumulative rate is same as ratePeSec
240: currentCumulativeRate = ratePerSec;
241: } else {
242: // Find time interval between last event and now
243: uint256 timeInterval = uint128(block.timestamp) - lastEventTime;
244: //calculate cumulative rate
245: currentCumulativeRate = lastCumulativeRate * _rpow(ratePerSec, timeInterval, RATE_PRECISION);
246: currentCumulativeRate = currentCumulativeRate / RATE_PRECISION;
247: }
248: return currentCumulativeRate;
249: }Let's further discuss the exact situation on the attack path to describe the issue on more details.
Internal pre-conditions
If the cumulative rate exceed certain percentage like 125% in my sample issue, it will cause revert to the cds profits computation.
Attack Path
Let's consider this scenario wherein the
Borrower deposit details at time of loan creation: Deposited amount in eth = 50 eth Ether price at deposit time = 3000 usd price of 1 Eth per oracle during deposit
During time of liquidation lastCumulativerate is 126% or 1.26e27
- Let's compute first the returnToTreasury or borrower's debt during time of liquidation. In this case, we use 126% to represent the borrower's debt value in percentage. It increased to that figure due to interest accumulated since loan creation up to liquidation. The amount of returnToTreasury here is 1.512e11 or 151,200 usd image
- Let's compute the returnToabond as part also of formula, in this case, we already encountered a problem of underflow error due to subtraction of larger returnToTreasury. See below image
- As you can see in this CDS profit computation below, there is already underflow error when the bigger returnToTreasury is subtracted to deposited amount value. image
Here is the link of google sheet for computation copy
Impact
Interruption of liquidation type 1 process. The loan is not liquidatable.
PoC
See attack path
Mitigation
Revise the computation of cds profits, it should not revert in any way possible so the liquidation won't be interrupted.