Core Contracts

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

finalizeLiquidation() will revert as it tries to pull crvUSD from StabililtyPool

Description

L525 inside finalizeLiquidation() tries to transfer crvUSD from stability pool to itself. Also, prior to this L457-L461 inside liquidateBorrower() of StabilityPool.sol checks crvUSD balance & grants allowance to LendingPool.sol:

File: contracts/core/pools/LendingPool/LendingPool.sol
496: function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
497: if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
498:
499: // update state
500: ReserveLibrary.updateReserveState(reserve, rateData);
501:
502: if (block.timestamp <= liquidationStartTime[userAddress] + liquidationGracePeriod) {
503: revert GracePeriodNotExpired();
504: }
505:
506: UserData storage user = userData[userAddress];
507:
508: uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
509:
510:
511: isUnderLiquidation[userAddress] = false;
512: liquidationStartTime[userAddress] = 0;
513: // Transfer NFTs to Stability Pool
514: for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
515: uint256 tokenId = user.nftTokenIds[i];
516: user.depositedNFTs[tokenId] = false;
517: raacNFT.transferFrom(address(this), stabilityPool, tokenId);
518: }
519: delete user.nftTokenIds;
520:
521: // Burn DebtTokens from the user
522: (uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
523:
524: // Transfer reserve assets from Stability Pool to cover the debt
525:@---> IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
526:
527: // Update user's scaled debt balance
528: user.scaledDebtBalance -= amountBurned;
529: reserve.totalUsage = newTotalSupply;
530:
531: // Update liquidity and interest rates
532: ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
533:
534:
535: emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
536: }

and

File: contracts/core/pools/StabilityPool/StabilityPool.sol
449: function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
450: _update();
451: // Get the user's debt from the LendingPool.
452: uint256 userDebt = lendingPool.getUserDebt(userAddress);
453: uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
454:
455: if (userDebt == 0) revert InvalidAmount();
456:
457:@---> uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
458: if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
459:
460: // Approve the LendingPool to transfer the debt amount
461:@---> bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
462: if (!approveSuccess) revert ApprovalFailed();
463: // Update lending pool state before liquidation
464: lendingPool.updateState();
465:
466: // Call finalizeLiquidation on LendingPool
467: lendingPool.finalizeLiquidation(userAddress);
468:
469: emit BorrowerLiquidated(userAddress, scaledUserDebt);
470: }

However there is no flow or function which deposits crvUSD into StabilityPool.sol at any point of time. The deposit by a user is of rToken (and RAAC reward tokens also make their way into the contract).

File: contracts/core/pools/StabilityPool/StabilityPool.sol
170: /**
➡️➡️ 171: * @notice Allows a user to deposit rToken and receive deToken.
172: * @param amount Amount of rToken to deposit.
173: */
174: function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
175: _update();
176: rToken.safeTransferFrom(msg.sender, address(this), amount);
177: uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
178: deToken.mint(msg.sender, deCRVUSDAmount);
179:
180: userDeposits[msg.sender] += amount;
181: _mintRAACRewards();
182:
183: emit Deposit(msg.sender, amount, deCRVUSDAmount);
184: }

So the code will fail at both the aforementioned points:

  • At L458 inside liquidateBorrower() with reason InsufficientBalance

  • At L525 inside finalizeLiquidation()

Impact

Can't liquidate a bad debt.

Mitigation

  • Either pull crvUSD into the StabilityPool contract from LendingPool when rTokens are deposited

  • OR add a function inside StabilityPool which can sell the collateral NFTs transferred into it to increase its crvUSD balance

  • OR change the mechanics to operate on rToken instead of crvUSD

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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.