Summary
The totalSupply function in the DebtToken contract incorrectly divides the total supply by the normalized debt instead of multiplying it. And, in the LendingPool contract, the borrow and repay functions equate totalSupply to totalUsage, which affects the utilization rate and the accounting of the protocol.
Vulnerability Details
The current totalSupply function in the DebtToken contract is:
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}
The totalSupply function incorrectly divides the total supply by the normalized debt. It should multiply the total supply by the normalized debt.
In the LendingPool contract, the borrow and repay functions equate totalSupply to totalUsage:
reserve.totalUsage = newTotalSupply;
reserve.totalUsage = newTotalSupply;
This affects the utilization rate and the accounting of the protocol.
Links to the issues:
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/DebtToken.sol#L233
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L360
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L424
POC
copy paste this below test in LendingPooin Borrow and Repayblock
it.only("Incorrect TotalSupply calculation", async function () {
const borrowAmount = ethers.parseEther("50");
console.log("total supply of Debt Token before borrow", formatEther(await debtToken.totalSupply()));
console.log("user 1 debt balance before borrow", formatEther(await debtToken.balanceOf(user1.address)));
await lendingPool.connect(user1).borrow(borrowAmount);
console.log("total supply of Debt Token after borrow", formatEther(await debtToken.totalSupply()));
console.log("user 1 debt balance after borrow", formatEther(await debtToken.balanceOf(user1.address)));
const crvUSDBalance = await crvusd.balanceOf(user1.address);
expect(crvUSDBalance).to.equal(ethers.parseEther("1050"));
const debtBalance = await debtToken.balanceOf(user1.address);
const totalSupply = await debtToken.totalSupply();
expect(debtBalance).to.gt(totalSupply);
});
The Output should be
total supply of Debt Token before borrow 0.0
user 1 debt balance before borrow 0.0
total supply of Debt Token after borrow 49.999999524353122506
user 1 debt balance after borrow 50.0
Impact
This issue can lead to incorrect calculations of the total supply and utilization rate, potentially causing issues with token accounting and the overall protocol's functionality.
Tools Used
Manual code review.
Recommendations
Update the totalSupply function to correctly multiply the total supply by the normalized debt. Additionally, update the borrow and repay functions in the LendingPool contract to correctly handle the totalUsage.
Corrected totalSupply Function in DebtToken
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
This ensures that the correct calculations are made for the total supply and utilization rate, leading to accurate token accounting and protocol functionality.