Core Contracts

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

DebtToken totalSupply() will always return an amount lower than expected

Summary

Inside the DebtToken's implementation we can see that balanceOf(user) returns the user's balance multiplied by the lending pool's usageIndex which is also stated in the comment above it.

/**
* @notice Returns the scaled debt balance of the user
* @param account The address of the user
* @return The user's debt balance (scaled by the usage index)
*/
function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}

DebtToken.sol#223

Vulnerability Details

The problem is that totalSupply() actually divides the total supply by the lending pool's usageIndex which makes it return less than expect. This is further verified by the comment above the function.

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

DebtToken.sol#232

Impact

With this current implementation totalSupply() will always be less than the sum of all user's balanceOf(user) which creates an inconsistency though the protocol.

Proof Of Concept

Click to reveal PoC

Place the following test case in LendingPool.test.js below describe("Borrow and Repay":

it.only("should showcase DebtToken inconsistency", async function () {
// totalSupply is zero, user1 balance is zero
let totalSupply = await debtToken.totalSupply();
let balance = await debtToken.balanceOf(user1.address);
expect(totalSupply).to.equal(0);
expect(balance).to.equal(0);
​
// User1 borrows 25e18 tokens (Any amount works!)
const borrowAmount = ethers.parseEther("25");
await lendingPool.connect(user1).borrow(borrowAmount);
​
totalSupply = await debtToken.totalSupply();
balance = await debtToken.balanceOf(user1.address);
​
console.table({ totalSupply, balance });
​
// totalSupply is always less than all users' balances
expect(totalSupply).to.be.lt(borrowAmount);
});

Recommendation

Instead of dividing the total supply by the usage index, it should be multiplied by it.

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!