Core Contracts

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

Borrower's NFT Collateral can Never be Withdrawn

Summary

The _repay function, and the finalizeLiquidation function both minus the DebtToken burned from the reserve asset scaled balance. This causes wrong accounting in which even though the borrower burns the entire DebtToken minted for them when they initially borrow the reserve asset, the user.scaledDebtBalance will never be 0. This will trap the borrower's NFT in the protocol.

Vulnerability Details

In the _repay function, the borrower repays (with interest) the reserve asset loan that they had taken. It has the following lines of code:

function _repay(uint256 amount, address onBehalfOf) internal {
...
reserve.totalUsage = newTotalSupply;
@> user.scaledDebtBalance -= amountBurned;
...

As shown above, the user.scaledDebtBalance minuses the amountBurned from it.

In this function, the amountBurned is the number of DebtToken owned by the borrower that is burned according to the amount of loan they want to pay back.

The user.scaledDebtBalance is the amount of reserve asset + Interest that the user needs to pay back to fully pay the loan.

So, as you can see, the DebtToken burned is minused from the reserve asset + Interest which is wrong.

To drive this point home, lets look at the borrow function where the scaledDebtBalance is updated initially:

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
...
uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
...
user.scaledDebtBalance += scaledAmount;
...

As shown above, the amount is the amount of reserve asset the borrower wants to borrow from the platform, and the scaledAmount is the amount / usageIndex which includes the current interest.

Hence, we can see that the amount of reserve asset + interest is added to the scaledDebtBalance in the borrow function, but the amount of DebtTokens burned is minused from the scaledDebtBalance in the _repay function.

The same bug is also present in the finalizeLiquidation function as shown below:

function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
...
user.scaledDebtBalance -= amountBurned;
...

Impact

Since the number of DebtTokens is actually the 1:1 representation of the number of reserve asset borrowed initially without the interest, this error will ensure that the user.scaledDebtBalance will never reach 0 even if the borrower burns all of the DebtTokens they received. If it never reaches 0, the borrower will never be able to withdraw their NFT they transferred as collateral.

To showcase this, imagine a situation where a borrower has burned all of their DebtTokens, but the scaledDebtBalance still has some value in it. When they call the withdrawNFT function, it will trigger the following code snippet:

// Check if withdrawal would leave user undercollateralized
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 nftValue = getNFTPrice(tokenId);
if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}

In our hypothetical situation, the userDebt will be the interest amount left after removing all the DebtToken burned. Let's say that the borrower only has 1 NFT as collateral. So the comparison shown above will be as follows:

collateralValue - nftValue < userDebt.percentMul(liquidationThreshold))
0 < non_zero_userDebt

The function will always revert due to the amount leftover in the scaledDebtBalance.

The borrower will never be able to pay back the debt because they will not have any more DebtTokens, and they will not be able to remove their real estate NFT without clearing the debt.

Tools Used

Manual Review

Recommendations

Since the scaledDebtBalance increases over time because of the usageIndex, I am not sure how to implement this correctly. Currently, the scaledDebtBalance only stores the interest when they initially borrow the tokens, but this will not hold true with time.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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