Core Contracts

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

The system’s solvency is compromised with repeated liquidations.

Summary

The StabilityPool uses its crvUSD balance to liquidate borrower debt but has no mechanism to replenish the spent crvUSD. Over time, this drains the pool.

Vulnerability Details

/**
* @notice Liquidates a borrower's position.
* @dev This function can only be called by a manager or the owner when the contract is not paused.
* @param userAddress The address of the borrower to liquidate.
* @custom:throws InvalidAmount If the user's debt is zero.
* @custom:throws InsufficientBalance If the Stability Pool doesn't have enough crvUSD to cover the debt.
* @custom:throws ApprovalFailed If the approval of crvUSD transfer to LendingPool fails.
* @custom:emits BorrowerLiquidated when the liquidation is successful.
*/
function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
// 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();
// Update lending pool state before liquidation
lendingPool.updateState();
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}

Assume:

  • StabilityPool holds 1000 crvUSD.

  • Borrower A has a debt of 200 crvUSD (scaled to 200e18 via WadRayMath).

A manager calls liquidateBorrower(A):

  • 200 crvUSD is transferred from the StabilityPool to the LendingPool.

  • StabilityPool balance: 1000 - 200 = 800 crvUSD.

Consider repeated liquidations and the effects on the stability pool.

The StabilityPool only spends crvUSD during liquidations but never refills it. There are no functions to:

  • Deposit crvUSD directly into the pool.

  • Recover crvUSD from liquidated collateral (e.g., selling collateral assets for crvUSD).

  • Collect fees from loans to replenish the pool.

Impact

The system’s solvency is compromised with repeated liquidations.

Tools Used

Foundry

Recommendations

Recover crvUSD from Liquidated Collateral
Add Replenishment Function

Updates

Lead Judging Commences

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

StabilityPool design flaw where liquidations will always fail as StabilityPool receives rTokens but LendingPool expects it to provide crvUSD

Support

FAQs

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

Give us feedback!