Core Contracts

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

Incorrect Return Value Calculation in `DebtToken::totalSupply()`

Summary

The DebtToken::totalSupply() function incorrectly calculates the total supply due to the use of an incorrect mathematical operation. This results in a return value that does not accurately reflect the total supply.

Vulnerability Details

As indicated by the @> marker, the function incorrectly applies rayDiv instead of rayMul, leading to an incorrect computation of the total supply.

/**
* @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());
}

Poc

Add the following test case to test/unit/core/pools/LendingPool/LendingPool.test.js and execute it:

describe("DebtToken totalSupply()", function () {
beforeEach("Simulate real-world interest rates", async function () {
// user2 deposits 1000e18 crvusd
const depositAmount = ethers.parseEther("1000");
await crvusd.connect(user2).approve(lendingPool.target, depositAmount);
await lendingPool.connect(user2).deposit(depositAmount);
// user1 deposits NFT
const tokenId = 1;
await raacNFT.connect(user1).approve(lendingPool.target, tokenId);
await lendingPool.connect(user1).depositNFT(tokenId);
// user1 borrows 20e18 crvusd
const borrowAmount = ethers.parseEther("20");
await lendingPool.connect(user1).borrow(borrowAmount);
await lendingPool.connect(user1).updateState();
// After 365 days
await ethers.provider.send("evm_increaseTime", [365 * 24 * 60 * 60]);
await ethers.provider.send("evm_mine", []);
await lendingPool.connect(user1).updateState();
});
it("output:", async function () {
console.log("balanceOf(user1.address):", await debtToken.balanceOf(user1.address));
console.log("totalSupply()", await debtToken.totalSupply()); // ❌
console.log("scaledBalanceOf(user1.address):", await debtToken.scaledBalanceOf(user1.address));
console.log("scaledTotalSupply():",await debtToken.scaledTotalSupply());
});
});

output:

LendingPool
DebtToken totalSupply()
Promise { <pending> }
balanceOf(user1.address): 20525536116956021902n
totalSupply() 19487919531825889295n # ❌ Incorrect: totalSupply() != balanceOf(user1.address)
scaledBalanceOf(user1.address): 19999999904870624275n
scaledTotalSupply(): 19999999904870624275n
✔ output:

Impact

Due to the incorrect mathematical operation (rayDiv instead of rayMul), the totalSupply() function returns an inaccurate value. This can lead to inconsistencies in lending pool calculations, affecting the accuracy of debt-related metrics.

Tools Used

Manual Review

Recommendations

Modify the function to use rayMul instead of rayDiv:

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

test again:

LendingPool
DebtToken totalSupply()
Promise { <pending> }
balanceOf(user1.address): 20525536117137245878n
totalSupply() 20525536117137245878n # ✅ Correct output
scaledBalanceOf(user1.address): 19999999904870624275n
scaledTotalSupply(): 19999999904870624275n
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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 4 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.