20,000 USDC
View results
Submission Details
Severity: medium
Valid

Precision loss leads to inaccurate pool values

Summary

The _calculateInterest function is vulnerable to precision loss. Due to dividing twice the function will return zero for both interest and protocolInterest. This could lead to a lender giving their loan to another pool that doesn't have the balance to cover it. Leading to a loss for them and a gain for the future pools as they will have debts greater than their balance.

Vulnerability Details

giveLoans() calls _calculateInterest() which then returns the amount of interest the protocol fee and adds that to the loan (debt) in order to calculate the totalDebt.
However, we already determined that these values are vulnerable to precision loss leading them to return 0 in some cases or be smaller than expected. That would lead to an inaccurate totalDebt allowing us to bypass the check in giveLoans that requires poolBalance to be greater than totalDebt.

uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
if (pool.poolBalance < totalDebt) revert PoolTooSmall();

Impact

Alice creates a pool with 100 dollars in it.

Let's say Bob takes out a loan for 100 dollars in debt.

This loan has a five percent interest rate leaving Bob owing a total of 105 after a year.

Alice decides she wants to get rid of the loan so she looks for another pool to give it to.

In theory, the protocol should only allow Alice to give the loan to a pool that has greater then a 105 dollar balance.

However when calling giveloans the function will call calculateInterest to calculate the interest to be 0 and assume totalDebt is 100. This would allow Alice to give her loan to a pool that has 101 or more dollars in it which isn't the intended behavior. The effect here would be that the user would be able to get an interest-free loan and the outstandingLoans value would be incorrect. Alice of course would lose the interest and now you have a loans that have bad debt or miscalculated values in outstandingLoans breaking trust in the entire system. This is also true of the buyLoans function refinanceFunction and any other function that calls _calculateInterest in order to calculate the totalDebt. There are multiple values throughout the contract that depend on totalDebt to be accurate as you can see below from the giveLoan function.

_updatePoolBalance(poolId, pool.poolBalance - totalDebt);
pools[poolId].outstandingLoans += totalDebt;

Recommended Steps

Verifying that neither the interest rate nor the fees are zero can help prevent precision loss and trigger a reversal if necessary. Additionally, establishing a minimum loan size could ensure that the left side of the equation consistently exceeds the right side.

interest = (l.interestRate * l.debt * timeElapsed) / 10000 / 365 days;
fees = (lenderFee * interest) / 10000;
if(interest == 0 || fees == 0) revert PrecisionLoss();
interest -= fees;

Another solution is creating a formula that always rounds up. This is in favor of the lender and the protocol as well. Something like this would suffice.

/**
* @param a numerator
* @param b denominator
* @dev Division, rounded up
*/
function roundUpDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
return (a - 1) / b + 1;
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.