Core Contracts

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

Incorrect Parameter in `LiquidationFinalized` Event Emission

Summary

The LendingPool::finalizeLiquidation() function incorrectly passes parameters to the LiquidationFinalized() event. Specifically, the function calls getUserCollateralValue(userAddress) after clearing the user.nftTokenIds storage, leading to an incorrect collateralLiquidated value of 0.

Vulnerability Details

The finalizeLiquidation() function is responsible for liquidating a user after the grace period expires. During execution, all of the user's NFTs are transferred to the stabilityPool, and their nftTokenIds storage is deleted. Consequently, calling getUserCollateralValue(userAddress) after this operation results in an incorrect collateral value of 0.

// LendingPool::finalizeLiquidation()
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);
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
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));
}
// LendingPool::getUserCollateralValue()
function getUserCollateralValue(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
uint256 totalValue = 0;
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
@> uint256 tokenId = user.nftTokenIds[i];
uint256 price = getNFTPrice(tokenId);
totalValue += price;
}
return totalValue;
}
/**
* @notice Emitted when a liquidation is finalized
* @param liquidator The address of the liquidator
* @param user The address of the user being liquidated
* @param debtRepaid The amount of debt repaid
@> * @param collateralLiquidated The amount of collateral liquidated
*/
event LiquidationFinalized(address indexed liquidator, address indexed user, uint256 debtRepaid, uint256 collateralLiquidated);

Impact

The incorrect emission of the LiquidationFinalized() event could lead to inconsistencies in on-chain event logs, which may impact external monitoring, analytics, and integrations relying on accurate event data.

Tools Used

Manual Review

Recommendations

Modify finalizeLiquidation() to cache the user's collateral value before deleting user.nftTokenIds:

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);
+ uint256 collateralLiquidated = getUserCollateralValue(userAddress);
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
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));
+ emit LiquidationFinalized(stabilityPool, userAddress, userDebt, collateralLiquidated);
}
Updates

Lead Judging Commences

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

LendingPool::finalizeLiquidation emits 0 collateralLiquidated because it deletes the info required to compute it

Support

FAQs

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