Core Contracts

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

Incorrectly calculated totalUsage leads to lower than expected borrow and liquidity rates

Summary

ReserveLibrary tracks reserve.totalUsage and reserve.totalLiquidity

  • totalLiquidity - amount of token assets, which were deposited, but not borrowed

  • totalUsage - amount of borrowed token assets

These two values are used to calculate utilizationRate, and base on this debt and income indexes are calculated.

More usage token in compare to liquidity = higher borrowing rates

Vulnerability Details

In all related function (borrow, _repay, finalizeLiquidation) reserve.totalUsage should be assigned with a new amount of borrowed tokens, but instead it is assigned with totalSupply minus accrued debt, because both mint and burn function are using totalSupply which is dividing totalSupply by normalized debt.

// LendingPool.sol 325
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);
...
// reserve.totalUsage += amount;
reserve.totalUsage = newTotalSupply;
}
// LendingPool.sol 398
function _repay(uint256 amount, address onBehalfOf) internal {
...
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
...
reserve.totalUsage = newTotalSupply;
}
// LendingPool.sol 496
function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
...
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
...
reserve.totalUsage = newTotalSupply;
}
// DebtToken.sol 136
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
...
if (amount == 0) {
return (false, 0, totalSupply());
}
...
return (scaledBalance == 0, amountToMint, totalSupply());
}
// DebtToken.sol 181
function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (amount == 0) {
return (0, totalSupply(), 0, 0);
}
...
return (amount, totalSupply(), amountScaled, balanceIncrease);
}
// DebtToken.sol 232
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}

Impact

totalUsagewill be always smaller than it should be, so as mentioned in the beginning. Borrowing and liquidity rates will be lower than expected, because utilization utilizationRate will be smaller

Tools Used

Manual Review

Recommendations

Use amount of tokens not divided by debt index.

Updates

Lead Judging Commences

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

DebtToken::mint returns totalSupply() instead of scaledTotalSupply(), causing incorrect updates to reserve.totalUsage in LendingPool.borrow()

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

DebtToken::mint returns totalSupply() instead of scaledTotalSupply(), causing incorrect updates to reserve.totalUsage in LendingPool.borrow()

Support

FAQs

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