Core Contracts

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

Variable Assignment Error Causes Borrowers to Never Have to Pay Interest on their Loans

Summary

The _repay and finalizeLiquidation functions have the wrong sequence of returned values from the DebtToken::burn function which causes the borrowers to never have to pay interest on the reserve assets they have borrowed.

Vulnerability Details

The burn function of the DebtToken returns the following variables:

function burn(
...
_burn(from, amount.toUint128());
...
return (amount, totalSupply(), amountScaled, balanceIncrease);
}

Note the order of the variables returned by the function.

When the same function is called by the LendingPool::repay contract, it has the following code snippet:

function _repay(uint256 amount, address onBehalfOf) internal {
...
1> (uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
// Transfer reserve assets from the caller (msg.sender) to the reserve
2> IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
...

In line 1 above, it has the places of amountScaled and amountBurned swapped. Let's keep note that from this line onwards, the variable named amountScaled is actually the amountBurned and the amountBurned is actually the amountScaled.

In line 2 shown above, we can see that the reserve asset is transferred from the msg.sender to the reserve address since this is the debt repay function. It transfers the amountScaled which, keep in mind is actually the amount of DebtTokens burned. So, this is the wrong amount of tokens to transfer back.

This actually happens in the liquidation function as well, as shown below:

function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
...
// Burn DebtTokens from the user
1> (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
2> IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
...

Impact

This bug causes the wrong amount to be transferred back when the borrower calls the repay function. Initially, when the borrow function is called, they mint the DebtToken in a 1:1 ratio to the reserve asset before any interest is applied to it. Because of that, the platform will never be able to collect interest, and the borrower will always return only the principal amount of the loan.

Tools Used

Manual Review.

Recommendations

Just need to ensure the correct sequence of the returned variables.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

Support

FAQs

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