Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Invalid

The owner or manager can not liquidate through `StabilityPool.sol#liquidateBorrower()`.

Summary

The owner or manager can not liquidate through StabilityPool.sol#liquidateBorrower().

Vulnerability Details

StabilityPool.sol#liquidateBorrower() function is as follows.

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
// Get the user's debt from the LendingPool.
@> uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
if (userDebt == 0) revert InvalidAmount();
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
// Approve the LendingPool to transfer the debt amount
bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
if (!approveSuccess) revert ApprovalFailed();
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}

Here, LendingPool.sol#getUserDebt() function is as follows.

File: LendingPool.sol
function getUserDebt(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
@> return user.scaledDebtBalance.rayMul(reserve.usageIndex);
}

As we can see above, it does not consider debt increased by elapsed time.

File: LendingPool.sol
function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
// update state
@> ReserveLibrary.updateReserveState(reserve, rateData);
if (block.timestamp <= liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodNotExpired();
}
UserData storage user = userData[userAddress];
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
...
}

Here, ReserveLibrary.sol#updateReserveState() function is as follows.

function updateReserveState(ReserveData storage reserve,ReserveRateData storage rateData) internal {
@> updateReserveInterests(reserve, rateData);
}
...
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();
// Update liquidity index using linear interest
reserve.liquidityIndex = calculateLiquidityIndex(
rateData.currentLiquidityRate,
timeDelta,
reserve.liquidityIndex
);
// Update usage index (debt index) using compounded interest
@> reserve.usageIndex = calculateUsageIndex(
rateData.currentUsageRate,
@> timeDelta,
reserve.usageIndex
);
// Update the last update timestamp
reserve.lastUpdateTimestamp = uint40(block.timestamp);
emit ReserveInterestsUpdated(reserve.liquidityIndex, reserve.usageIndex);
}

From upper implementation, if time is elapsed from reserve.lastUpdateTimestamp the owner or manager cannot liquidate through StabilityPool.sol#liquidateBorrower() function.

Impact

If time is elapsed from reserve.lastUpdateTimestamp the owner or manager cannot liquidate through StabilityPool.sol#liquidateBorrower() function.

Tools Used

Manual review

Recommendations

Modify LendingPool.sol#getUserDebt() function so that it returns current usageIndex by considering elapsed time.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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

Give us feedback!