Core Contracts

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

DebtToken::totalSupply returns incorrect information

Summary

In DebtToken::totalSupply , the total supply is incorrectly calculated by dividing an already scaled total supply value by the usage index. This results in an incorrect total supply value, which can lead to miscalculations in the contract's logic, particularly in functions that rely on the accurate total supply of debt tokens.

Vulnerability Details

DebtToken::totalSupply contains the following code:

/**
* @notice Returns the scaled total supply
* @return The total supply (scaled by the usage index)
*/
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}

The actual totalsupply is derived in this function by dividing an already scaled total supply value by the usage index. It is already scaled because when a user borrows a token via LendingPool::borrow, they are minted DebtTokens and in the flow of DebtToken::mint , the overriden DebtToken::_update function is called which normalizes the amount of tokens minted before upating the user's balance. See below:

function _update(
address from,
address to,
uint256 amount
) internal virtual override {
if (from != address(0) && to != address(0)) {
revert TransfersNotAllowed(); // Only allow minting and burning
}
uint256 scaledAmount = amount.rayDiv(
ILendingPool(_reservePool).getNormalizedDebt()
);
super._update(from, to, scaledAmount);
emit Transfer(from, to, amount);
}

As a result, when the totalSupply is retrieved, it should be multiplied by the current usageIndex to get the actual total supply which isnt the case as seen above.

Proof Of Code (POC)

This test was run in protocols-test.js in the "StabilityPool" describe block

it("totalsupply incorrect value", async function () {
//c for testing purposes
//c borrow tokens to allow for usage index to update
const newTokenId = HOUSE_TOKEN_ID + 2;
await contracts.housePrices.setHousePrice(newTokenId, HOUSE_PRICE);
await contracts.crvUSD
.connect(user2)
.approve(contracts.nft.target, HOUSE_PRICE);
await contracts.nft.connect(user2).mint(newTokenId, HOUSE_PRICE);
await contracts.nft
.connect(user2)
.approve(contracts.lendingPool.target, newTokenId);
await contracts.lendingPool.connect(user2).depositNFT(newTokenId);
await contracts.lendingPool.connect(user2).borrow(BORROW_AMOUNT);
//c allow time to pass to update usage index
await time.increase(73 * 60 * 60);
const scaledbal = await contracts.debtToken.scaledBalanceOf(
user2.address
);
console.log(`totalsupply: ${scaledbal}`);
const reservedata = await contracts.lendingPool.getAllUserData(
user2.address
);
const usageindex = reservedata.usageIndex;
const expectedtotalsupply = await contracts.reserveLibrary.raymul(
scaledbal,
usageindex
);
console.log("expectedtotalsupply: ", expectedtotalsupply);
const actualtotalsupply = await contracts.debtToken.totalSupply();
console.log("actualtotalsupply: ", actualtotalsupply);
assert(actualtotalsupply < expectedtotalsupply);
});

Impact

Incorrect Total Supply: The totalSupply function returns an incorrect value, which is smaller than the actual total supply of debt tokens. This can lead to miscalculations in functions that rely on the total supply, such as interest accrual, liquidation, or stability pool calculations.

Miscalculations in Contract Logic: Functions that depend on the total supply (e.g., calculating interest rates, distributing rewards, or determining liquidation thresholds) may behave incorrectly, leading to financial losses or unintended behavior.

Tools Used

Manual Review, Hardhat

Recommendations

Fix the totalSupply Function: The totalSupply function should multiply the scaled total supply by the usage index instead of dividing it. Update the function as follows:

function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
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!