Core Contracts

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

`DebtToken::totalSupply` should be multiplied by index instead of divided by index

Summary

LendingPoolcontract are using reserve.totalUsage that populated by returned value of DebtToken::totalSupply when calculating the utilization rate, but the implementation of DebtToken::totalSupplyis wrong leading to incorrect state used for the whole protocol core function.

Vulnerability Details

ReserveLibrary::updateInterestRatesAndLiquidityis a part of core function borrow, _repay, finalizeLiquidationwhere it is needed to update the state of interest rate and the utilization rate.

the above three function have the same pattern, where they first update the reserve.totalUsagebefore calling ReserveLibrary.updateInterestRatesAndLiquidity, I provide the example in borrow function:

LendingPool.sol#L325-L369

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
.
.
.
@> (bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
// Transfer borrowed amount to user
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
user.scaledDebtBalance += scaledAmount;
// reserve.totalUsage += amount;
@> reserve.totalUsage = newTotalSupply;
// Update liquidity and interest rates
@> ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, 0, amount);
.
.
.

we can notice that newTotalSupplyvalue is returned by the mint function of DebtToken. let's dive into the function:

DebtToken.sol#L136-L168

function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
.
.
.
@> return (scaledBalance == 0, amountToMint, totalSupply());
}

the value are from totalSupply function like below:

DebtToken.sol#L232-L235

function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
@> return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}

this is where the issue happen, the super.totalSupplyvalue is already on it scaled version but the function totalSupply is dividing super.totalSupply amount by the current index instead of multiplying it before return it. The correct amount returned should be the normalized version, because it would reflect the underlying asset borrowed by the borrower plus the interest accrued.

Impact

this clearly an issue because of reserve.totalUsagewould later have lower amount than it supposed to be, leading to the protocol thinking that it utilization rate are below the what it supposed to be, this can lead to wrong index which can affect the accuracy of whole protocol.

Tools Used

manual review

Recommendations

use rayMul instead of rayDiv:

diff --git a/contracts/core/tokens/DebtToken.sol b/contracts/core/tokens/DebtToken.sol
index e70313d..8eaa71e 100644
--- a/contracts/core/tokens/DebtToken.sol
+++ b/contracts/core/tokens/DebtToken.sol
@@ -231,7 +239,7 @@ contract DebtToken is ERC20, ERC20Permit, IDebtToken, Ownable {
*/
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
- return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
+ return scaledSupply.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

Support

FAQs

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