Core Contracts

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

Not updating the index will return a stale index and this is used to get debt amount used for approval causing all liquidation calls to fail

Summary

The debt amount of the user will return the actual amount a user is owning but because of the lack of a previous update call, this amount will be smaller than the actual amount causing the liquidation debt causing the transaction to revert due to insufficient allowance.

Vulnerability Details

Update call is made after userdebt has been approved

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
// Get the user's debt from the LendingPool.
@audit>> uint256 userDebt = lendingPool.getUserDebt(userAddress);
@audit>> 1. uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt()); // update update state before we call getindex update before call
if (userDebt == 0) revert InvalidAmount();
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
@audit>> 2. if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
// Approve the LendingPool to transfer the debt amount
@audit>> 3. bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
if (!approveSuccess) revert ApprovalFailed();
// Update lending pool state before liquidation
@audit>> 4. lendingPool.updateState(); // bug from former code fixed damn
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}

The index returned will be state has there is no update call before this and hence the debt amount approved will be lesser than the actual amount needed.

@audit 4. should be done first else the entire checks and approval will not work accordingly.

Debt amount calculated will not be accurate

  1. Alice debt is liquidatable

  2. present index is 1.208163179e27 ray

  3. with a debt of 1000e18 debt token.

  4. Stale index is 1.208012100e27

  5. Because there is no update call we calculate the debt amount based on the stale amount and approve Crvusd to cover that portion

  6. Call is made to lending pool to finalize liquidation

  7. The recent debt is calculated and amount to be send in is based on this amount causing the entire call to revert

  8. Note check @audit 3. will also not be adequate has index is stale

/**
* @notice Allows the Stability Pool to finalize the liquidation after the grace period has expired
* @param userAddress The address of the user being liquidated
*/
function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
// update state
@audit>> 1. ReserveLibrary.updateReserveState(reserve, rateData);
if (block.timestamp <= liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodNotExpired();
}
UserData storage user = userData[userAddress];
@audit>>2. uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
isUnderLiquidation[userAddress] = false;
liquidationStartTime[userAddress] = 0;
// Transfer NFTs to Stability Pool
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
user.depositedNFTs[tokenId] = false;
raacNFT.transferFrom(address(this), stabilityPool, tokenId);
}
delete user.nftTokenIds;
// Burn DebtTokens from the user
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
// Transfer reserve assets from Stability Pool to cover the debt
@audit>> IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
// Update user's scaled debt balance
user.scaledDebtBalance -= amountBurned;
reserve.totalUsage = newTotalSupply;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
}

Impact

Liquidation call will always revert because insufficient approval is given to the lending pool

Tools Used

Manual Review

Recommendations

Update the state before calculating the amount to approve for the liquidation process

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

StabilityPool: liquidateBorrower should call lendingPool.updateState earlier, to ensure the updated usageIndex is used in calculating the scaledUserDebt

Support

FAQs

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