Core Contracts

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

Liquidation DOS Due to Incorrect State Update Order

Summary

The liquidation mechanism in the Stability Pool contract can fail due to incorrect ordering of state updates and debt calculations. The user's scaled debt is calculated before updating the lending pool state, leading to potential discrepancies between the approved amount and the actual debt that needs to be repaid.

Vulnerability Details

The issue occurs in two parts:

  1. In StabilityPool.sol's liquidateBorrower():

function liquidateBorrower(address userAddress) external {
_update();
// Debt calculation BEFORE lending pool state update
uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
// ... approve scaledUserDebt ...
// State update happens AFTER debt calculation
lendingPool.updateState();
lendingPool.finalizeLiquidation(userAddress);
}
  1. In LendingPool.sol's finalizeLiquidation():

function finalizeLiquidation(address userAddress) external {
// State update changes usage index
ReserveLibrary.updateReserveState(reserve, rateData);
// New debt calculation with updated index
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
// Attempts to burn and transfer the new debt amount
// But StabilityPool only approved the old amount
}

The vulnerability arises because:

  1. StabilityPool calculates and approves debt based on old state

  2. LendingPool updates state, changing the actual debt amount

  3. The approved amount becomes insufficient for the actual debt

Impact

Liquidations will fail when debt amounts change due to state updates

Tools Used

Recommendations

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
// Update lending pool state FIRST
lendingPool.updateState();
// Calculate debt AFTER state update
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();
bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
if (!approveSuccess) revert ApprovalFailed();
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}
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.