Summary
Lending and borrowing protocols offer interest rates for depositors so that they provide liquidity. RAAC does the same, where the deposit interest rates are coming from the extra funds borrowers need to repay. The protocol uses a linear interest rate for deposit rates and a compounding rate for borrowers. These rates are updated after every meaningful action (i.e. deposit
, withdraw
, borrow
, repay
, and liquidate
). However, in the current system, the borrowing rate increases even if there are no loans in the system, making it extremely unfavorable for borrowers.
Vulnerability Details
Let's look at how interest rates are updated in RAAC:
function updateReserveInterests(ReserveData storage reserve,ReserveRateData storage rateData) internal {
uint256 timeDelta = block.timestamp - uint256(reserve.lastUpdateTimestamp);
if (timeDelta < 1) {
return;
}
uint256 oldLiquidityIndex = reserve.liquidityIndex;
if (oldLiquidityIndex < 1) revert LiquidityIndexIsZero();
reserve.liquidityIndex = calculateLiquidityIndex(
rateData.currentLiquidityRate,
timeDelta,
reserve.liquidityIndex
);
reserve.usageIndex = calculateUsageIndex(
rateData.currentUsageRate,
timeDelta,
reserve.usageIndex
);
reserve.lastUpdateTimestamp = uint40(block.timestamp);
emit ReserveInterestsUpdated(reserve.liquidityIndex, reserve.usageIndex);
}
function calculateLiquidityIndex(uint256 rate, uint256 timeDelta, uint256 lastIndex) internal pure returns (uint128) {
uint256 cumulatedInterest = calculateLinearInterest(rate, timeDelta, lastIndex);
return cumulatedInterest.rayMul(lastIndex).toUint128();
}
function calculateUsageIndex(uint256 rate, uint256 timeDelta ,uint256 lastIndex) internal pure returns (uint128) {
uint256 interestFactor = calculateCompoundedInterest(rate, timeDelta);
return lastIndex.rayMul(interestFactor).toUint128();
}
We can see that the update is happening only if sufficient time has passed, which is good. It also reverts if no proper rates are set. If we dig deeper to see how the liquidityIndex
is updated, we would see that it depends on the currentLiquidityRate
, which will always be 0
if there is no debt:
function calculateLiquidityRate(uint256 utilizationRate, uint256 usageRate, uint256 protocolFeeRate, uint256 totalDebt) internal pure returns (uint256) {
if (totalDebt < 1) {
@> return 0;
}
uint256 grossLiquidityRate = utilizationRate.rayMul(usageRate);
uint256 protocolFeeAmount = grossLiquidityRate.rayMul(protocolFeeRate);
uint256 netLiquidityRate = grossLiquidityRate - protocolFeeAmount;
return netLiquidityRate;
}
However, when calculating the usageIndex
, there are no checks for the debt is 0
, as the currentUsageRate
will increase with the baseRate
even with no loans in the system:
function calculateBorrowRate(
uint256 primeRate,
uint256 baseRate,
uint256 optimalRate,
uint256 maxRate,
uint256 optimalUtilizationRate,
uint256 utilizationRate
) internal pure returns (uint256) {
if (primeRate <= baseRate || primeRate >= maxRate || optimalRate <= baseRate || optimalRate >= maxRate) {
revert InvalidInterestRateParameters();
}
uint256 rate;
if (utilizationRate <= optimalUtilizationRate) {
uint256 rateSlope = primeRate - baseRate;
uint256 rateIncrease = utilizationRate.rayMul(rateSlope).rayDiv(optimalUtilizationRate);
@> rate = baseRate + rateIncrease;
} else {
uint256 excessUtilization = utilizationRate - optimalUtilizationRate;
uint256 maxExcessUtilization = WadRayMath.RAY - optimalUtilizationRate;
uint256 rateSlope = maxRate - primeRate;
uint256 rateIncrease = excessUtilization.rayMul(rateSlope).rayDiv(maxExcessUtilization);
rate = primeRate + rateIncrease;
}
return rate;
}
From the above, we can see that the borrowing rate will increase with a compounding rate, making it extremely unfavorable for borrowers.
If we look at how AAVE is doing it, we will see that these indexes increase only when debt is present.
Impact
Borrowers will pay increased rates from the very beginning, basically making them lose the incentive to join the protocol.
Tools Used
Manual review
Recommendations
Check if there is any debt in the system before updating the usageRate
.