Summary
The LendingPool.setProtocolFeeRate function sets the protocol fee rate but does not update the reserve applying the reserve interests for the period since the last update. This way the next update will apply the new protocolFeeRate retroactively.
Vulnerability Details
The LendingPool.setProtocolFeeRate does not update the reserve:
function setProtocolFeeRate(uint256 newProtocolFeeRate) external onlyOwner {
rateData.protocolFeeRate = newProtocolFeeRate;
}
The protocolFeeRate is used in the rateData.currentLiquidityRate value calculation.
ReserveLibrary.sol:
function updateInterestRatesAndLiquidity(ReserveData storage reserve,ReserveRateData storage rateData,uint256 liquidityAdded,uint256 liquidityTaken) internal {
<...>
rateData.currentLiquidityRate = calculateLiquidityRate(
utilizationRate,
rateData.currentUsageRate,
>> rateData.protocolFeeRate,
totalDebt
);
>> updateReserveInterests(reserve, rateData);
Then the new rateData.currentLiquidityRate is used in the updateReserveInterests function which updates the reserve interests and can be applied retroactively.
ReserveLibrary.sol:
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);
}
Impact
Unintended behavior due to retroactive application of the new protocolFeeRate, assets losses.
Tools used
Manual Review
Recommendations
Consider updating the reserve interests before setting a new protocolFeeRate:
function setProtocolFeeRate(uint256 newProtocolFeeRate) external onlyOwner {
+ ReserveLibrary.updateReserveState(reserve, rateData);
rateData.protocolFeeRate = newProtocolFeeRate;
}