Core Contracts

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

DebtToken.sol::totalSupply() returns the incorrect value, under-representing the total usage of the protocol.

Summary

The debtToken is used to represent the total borrowed by the protocol + interest owed to the protocol by the borrowers. Whenever the users borrow or repay, the total usage is updated. This value comes from the totalSupply() of the debtToken, which is meant to represent the total borrows + interest. However instead of multipying the total real supply of debtToken with the current usage index, which would gets the total borrow + interest, it divides them, under-reprenesting the total borrow + interests.

Vulnerability Details

In DebtToken.sol::totalSupply(), the real total supply of the DebtToken is dividied by the current usage Index.

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

which is returned by mint() and burn(), called when users borrow and repay

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

And the totalUsage is updated in LendingPool.sol::borrow() and repay().

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);
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
user.scaledDebtBalance += scaledAmount;
reserve.totalUsage = newTotalSupply;
....
}
function _repay(uint256 amount, address onBehalfOf) internal {
....
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
....
}

Impact

This under-representing of totalUsage affects the whole protocol, as it is used to determine the supply rates and interests rates as well as the liquidity and usage indexes. This will make the protocol earn less interest rates, allow the utilization to be over 100%, and supplier earn less interest rates.

Tools Used

Manual Review

Recommendations

Change the rayDiv to rayMul in DebtToken.sol::totalSupply(), to reflect the total borrowed + interests owed

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 7 months 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 7 months 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.

Give us feedback!